By source, the front end has two types of state to manage:
-
An intermediate state of user interaction
-
Server Status
In older projects, they are often treated equally with global state management solutions such as Redux and Mobx.
In fact, they are quite different:
An intermediate state of user interaction
For example, isLoading and isOpen of components have the following characteristics:
-
Update as synchronous
-
The state is completely controlled by the front end
-
Independent state (different components have their own isLoading)
This kind of state is usually kept inside the component.
When state needs to be passed across the component hierarchy, the Context API is usually used.
A global state management scheme such as Redux is used for a larger range of states.
Server Status
When we request data from the server:
function App() {
const [data, updateData] = useState(null);
useEffect(async() = > {const data = await axios.get('/api/user');
updateData(data);
}, [])
/ / the data processing
}
Copy the code
The returned data is typically stored inside the component as state (such as the data state of an App component).
If it is a generic state that needs to be reused, it is usually stored in a global state management scheme such as Redux.
There are two disadvantages to this:
- The request intermediate state needs to be processed repeatedly
To make App components robust, we also need to handle intermediate states such as request, error, and so on:
function App() {
const [data, updateData] = useState(null);
const [isError, setError] = useState(false);
const [isLoading, setLoading] = useState(false);
useEffect(async () => {
setError(false);
setLoading(true);
try {
const data = await axios.get('/api/user');
updateData(data);
} catch(e) {
setError(true);
}
setLoading(false);
}, [])
/ / the data processing
}
Copy the code
This kind of generic intermediate state handling logic can be written many times in different components.
- A cache is different in nature from a state
Unlike the intermediate state of an interaction, server-side state is better classified as a cache and has the following properties:
-
Requests and updates are usually made asynchronously
-
State is controlled by the requested data source, not by the front end
-
State can be shared by different components
As a cache that can be shared by different components, there are more issues to consider, such as:
-
Cache invalidation
-
The cache update
A Redux is handy. However, treating different types of state differently makes the project more manageable.
In this case, it is recommended to use React-Query to manage server status.
Another alternative is SWR. You can see the difference here
Meet the React – Query
React-query is a data request library based on hooks.
We can rewrite this example with a react-query:
import { useQuery } from 'react-query'
function App() {
const {data, isLoading, isError} = useQuery('userData'.() = > axios.get('/api/user'));
if (isLoading) {
return <div>loading</div>;
}
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>)}Copy the code
The Query in React-Query refers to the data source of an asynchronous request.
In this example, the userData string is the key unique to this Query.
As you can see, react-Query encapsulates the entire request intermediate state (isLoading, isError…). .
Not only that, react-Query does the following for us:
-
When multiple components request the same Query, only one request is made
-
Cache data invalidation/update policy (determine appropriate cache invalidation and automatically request data after invalidation)
-
Clean up invalid data garbage
CRUD of data is processed by two hooks:
-
UseQuery handles data lookup
-
UseMutation handles data addition, deletion, and modification
In the following example, clicking the Create user button initiates a POST request to create a user:
import { useQuery, queryCache } from 'react-query';
function App() {
const {data, isLoading, isError} = useQuery('userData'.() = > axios.get('/api/user'));
// Add a user
const {mutate} = useMutation(data= > axios.post('/api/user', data));
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
<button
onClick={()= >{mutate({name: 'kasong', age: 99})}} > Create a user</button>
</ul>)}Copy the code
However, the userData Query data is not updated when clicked, because it is not invalid.
So we need to tell the react-query that the cache corresponding to the userData Query is invalid and needs to be updated:
import { useQuery, queryCache } from 'react-query';
function App() {
// ...
const {mutate} = useMutation(userData= > axios.post('/api/user', userData), {
onSuccess: () = > {
queryCache.invalidateQueries('userData')}})// ...
}
Copy the code
The request is triggered by calling the mutate method.
After the request is successful, would trigger the onSuccess callback, the callback is called queryCache. InvalidateQueries, will the userData corresponding to invalidate the query cache.
In this case, react-Query will request userData’s Query data again.
conclusion
By using a data request library such as React-Query (or SWR), server-side state can be freed from global state.
This has brought us many benefits:
-
Handle request intermediate states using generic hooks
-
Redundant request merge
-
Update/invalidation policies for caching
-
Global state management solutions such as Redux can focus more on front-end intermediate state handling