directory
- Motivation & Goals
useHash
– Read-write hash valueuseHashSearchParams
– Read/write hash query parametersuseHashSearchParamsJSON
– Read and write hash query parameters in JSON format- summary
Motivation & Goals
In web single-page applications based on hash mode, it is often necessary to read and write hash query parameters, such as reading commodity IDS from hash query parameters. In React, if you parse window.location.hash directly, you don’t get updates when the hash changes. You also need to listen for the hashchange event to update the internal state. Furthermore, automatic type resolution and serialization of parameters can be added to support arbitrary parameter value types (including nested JSON objects), all of which can be implemented with a simple hook.
UseHashSearchParamsJSON hook reads and writes the specified query parameters and automatically resolves the correct type.
// http://a/b/c#/a/b/c? id=1&name="jim"
// Gets the specified query parameter value
const [id, setId] = useHashSearchParamsJSON('id')
console.log(id) / / 1
// Get all query parameters
const [params, setParams] = useHashSearchParamsJSON()
console.log(params) // {id: 1, name: "jim"}
// Update parameterssetParams({... params,id: 2})
// http://a/b/c#/a/b/c? id=2&name="jim"
Copy the code
The above hooks are not implemented in one step, but divided into three hooks of different granularity. UseHash; useHashSearchParams; userHashSearchParamsJSON; In addition to using these custom hooks in your own projects, you can also learn how to encapsulate your own hooks to extract reusable logic.
useHash
– Read-write hash value
Route parameters are contained in the HASH value of the URL. The basis for implementing read/write route parameters is to implement the hash value of the read/write route parameters.
// hash: string - hash string
// setHash: (hash: string) => void - Updates the hash string
const [hash, setHash] = useHash()
Copy the code
It returns two arguments. The first argument is the hash string in the current URL, and the second argument is the hash string update function, as shown in the following example:
// http://www.abc.com/#/path/to/page?id=1
const [hash, setHash] = useHash();
console.log(hash); // "#/path/to/page? id=1"
setHash("#/path/to/page? id=2");
console.log(hash); // "#/path/to/page? id=2"
Copy the code
Based on the above expected behavior, the code that implements useHash is as follows. The key point is that it automatically listens for hashchange events, eliminating the need for the user to write code repeatedly (only with a substantial reduction in effort and errors can you wrap a hook that someone else might want to use) :
import { useState, useCallback, useEffect } from "react";
/** * reads and writes the URL hash string and automatically responds to changes in the URL hash string */
export const useHash = () = > {
const [hash, setHash] = useState(() = > window.location.hash);
const onHashChange = useCallback(() = > {
setHash(window.location.hash); } []);// Listen for hash string changes
useEffect(() = > {
window.addEventListener("hashchange", onHashChange);
return () = > {
window.removeEventListener("hashchange", onHashChange); }; } []);const _setHash = useCallback(
(newHash: string) = > {
if(newHash ! == hash) {window.location.hash = newHash;
}
},
[hash]
);
return [hash, _setHash] as const;
};
Copy the code
The useHash implementation is integrated into the react-use repository, and can be directly used by importing the react-Use package.
useHashSearchParams
– Read/write hash query parameters
With useHash we can easily read and write URL hash values. Next we need to implement the query parameters in the hash format key1= Value1&key2 = Value2&… , the core of which is the resolution of key-value pairs in query parameters.
Hooks have the following prototype:
// value: string - Specifies the key value
// setValue: (value: string) => void - Updates the specified key value
const [value, setValue] = useHashSearchParams(key)
// query: Record
- All query parameters
,>
// setQuery: (query: Record
) => void - Updates query parameters
,>
const [query, setQuery] = useHashSearchParams()
Copy the code
It returns two arguments, the first representing the value of the specified key (or all key/value objects), and the second to update the value of the specified key (or update the entire query parameter). The following is an example:
// http://a/b/c#/a/b/c? id=1&name=jim
// Read a single query parameter
const [id, setId] = useHashSearchParams("id");
console.log(id); / / "1"
// Modify a single query parameter
setId(2);
console.log(id); / / "2"
// Read all query parameters
const [params, setParams] = useHashSearchParams();
console.log(params); // {id: "1", name: "jim"}
// Modify the entire query parameter
setParams({ name: "jack" });
console.log(params); // {id: "1", name: "jack"}
Copy the code
UseHash is used to obtain the URL hash value, then the query string in the hash is intercepted, and the URLSearchParams is used to parse it into a key-value pair and return it. Modify the key-value pairs while writing, then concatenate them into query strings and call setHash to write back the URL hash value. The key point here is to pay attention to the encoding and decoding of the key/value of the query parameter. When using URLSearchParams to parse the hash string, it decodes it for us. When serializing the query parameter into a hash string, we need to encode it ourselves. Also, be careful to distinguish between reading and writing individual or all query parameters.
The final code implementation is as follows:
import { useHash } from "./useHash";
import { useCallback } from "react";
// Override the function signature
interface UseHashSearchParamsType {
(key: string, defaultValue? :string) :readonly [string.(value: any) = > void]; () :readonly [
Record<string.string>,
(searchParams: Record<string.any>) = > void
];
}
export constuseHashSearchParams: UseHashSearchParamsType = ( key? :any, defaultValue? :any) :any= > {
const [hash, setHash] = useHash();
const questionIndex = hash.indexOf("?");
constsearch = questionIndex ! = = -1 ? hash.substring(questionIndex) : "";
// Parse the query parameters
const usp = new URLSearchParams(search);
const hashSearchParams: Record<string.string> = {};
usp.forEach((value, key) = > {
hashSearchParams[key] = value;
});
const setHashSearchParams = useCallback(
(searchParams: Record<string.any>) = > {
constsearchPrefix = (questionIndex ! = = -1 ? hash.slice(0, questionIndex) : hash.slice(0)) +
"?";
// concatenate query parameters and encode them
const search = Object.keys(searchParams).reduce((finalSearch, key) = > {
if (finalSearch) finalSearch += "&";
const value = String(searchParams[key]);
finalSearch += encodeURIComponent(key);
// remove '=' if param with empty value
if (value) {
finalSearch += "=" + encodeURIComponent(value);
}
return finalSearch;
}, "");
setHash(searchPrefix + search);
},
[hash, questionIndex, setHash]
);
if (key) {
return [
hashSearchParams[key] === undefined
? defaultValue
: hashSearchParams[key],
(value: any) = >setHashSearchParams({ ... hashSearchParams, [key]:String(value) }),
];
} else {
return[hashSearchParams, setHashSearchParams]; }};Copy the code
UseHashSearchParams has submitted PR to the react-use repository. After it is merged, you can import the react-Use package to use it
useHashSearchParamsJSON
– Read and write hash query parameters in JSON format
UseHashSearchParams can easily read and write query parameters in THE URL hash. However, the parameter values are all strings. In practice, type resolution is often required according to the field type, for example, http://a/b/c#/a/b/c? Id =1&name= Jim id is a numeric type, which needs to be parseInt first to parse into a number, which is very inconvenient for actual business development.
I expect route parameters to be self-contained data types and serialized to strings, and the best data format is JSON. Query parameters can be serialized to JSON as a hash string, which can be used to parse JSON to get the correct data type. At the same time, complex JSON objects can be stored in the hash, thus supporting the transfer of JavaScript pure objects between pages.
The hooks prototype is as follows:
// value: any - Specifies the key value of any type
// setValue: (value: any) => void - Updates the specified key value
const [value, setValue] = useHashSearchParamsJSON(key)
// query: Record
- All query parameters, where values can be of any type
,>
// setQuery: (query: Record
) => void - Updates query parameters
,>
const [query, setQuery] = useHashSearchParamsJSON()
Copy the code
The following is an example:
// http://a/b/c#/a/b/c? id=1&name="jim"
const [id, setId] = useHashSearchParamsJSON<number> ('id')
console.log(id) / / 1
const [name, setName] = useHashSearchParamsJSON<string> ('name')
console.log(name) // "jim"
const [params, setParams] = useHashSearchParamsJSON()
console.log(params) // {id: 1, name: "jim"}
setParams({empty: true})
console.log(params) // {id: 1, name: "jim", empty: true}
Copy the code
UseHashSearchParams is used to obtain the string representation of the query parameter value, and then json.parse is used to obtain the JS type. The write is serialized to a string representation via json.stringify and then written back to the hash string via the update function provided by useHashSearchParams.
The implementation code is as follows:
import { useCallback } from 'react'
import { useHashSearchParams } from 'react-use'
const mapValues = (src, fn) = >
Object.keys(src).reduce(
(target, key) = > Object.assign(target, { [key]: fn(src[key]) }),
{}
);
// Override the function signature
interface UseRouteParamsType {
<T = any>(key: string, defaultValue? : T): [T |undefined.(value: T) = > void]
<T = any>(): [Record<string, T | undefined>, (searchParams: Record<string, T>) = > void]}export constuseHashSearchParamsJSON: UseRouteParamsType = (key? :any, defaultValue? :any) :any= > {
const dispatch = useTDispatch()
// Get the value of the specified key in string format
const [json, setJson] = useHashSearchParams(key)
let value: any
// Call json. parse to parse the JSON string to the JS value. Parsing may fail and exception handling is required
try {
value = key
? json === undefined
? defaultValue
: JSON.parse(json)
: _.mapValues(json, (v) = > JSON.parse(v))
} catch (err) {
// debugErr(err)
}
// Call json.stringify to serialize the JS value into a JSON string
const setValue = useCallback(
(newValue: any) = > {
if (key) {
setJson(JSON.stringify(newValue))
} else {
setJson(_.mapValues(newValue, (v) = > JSON.stringify(v)))
}
},
[dispatch.shared, key, setJson, value]
)
return [value, setValue]
}
Copy the code
UseHashSearchParamsJSON is not applicable to react-Use. If necessary, you can implement the useHashSearchParamsJSON in the project by referring to the above code.
summary
The hooks that read and write route parameters implemented in this article are of great practical value in themselves, and can be used as a reference for learning about custom hooks. In addition, when you encounter hooks with complex functions, try breaking down the hooks as this article does and implementing them step by step: find the more general hooks(such as useHash) and implement the more specific hooks based on existing hooks(such as useHashSearchParams).