The main logic of the component is that when the page is loaded, need to save the parameters from the URL (token) and pass them from the dispatch()
function. To do this, I have a state value were initially null is stored, but after rendering the component I add a token.
component.js
const [state, dispatch] = useReducer()
const [value, setValue] = useState(null)
useEffect(() => {
const query = window.location.search || ''
const queryString = new URLSearchParams(query || '')
const token = queryString.get('token')
setValue(token)
}, [value])
useEffect(() => {
if (value && value !== null) {
// console.log(1)
dispatch({
type: 'TEST_TYPE',
payload: { data: value },
})
}
}, [value])
When I want to call the dispatch()
function in useEffect()
the component is reloaded until the memory is full. I tested the second method, called the dispatch()
function into useCallback()
to call once if there are new changes. But the dispatch
function is not called
const dis = useCallback(() => dispatch, [])
useEffect(() => {
const query = window.location.search || ''
const queryString = new URLSearchParams(query || '')
const token = queryString.get('token')
if (token) {
dis({
type: 'TEST_TYPE',
payload: { data: token },
})
}
}, [dis])
How can I properly make dispatch
work once when the component is loaded and without an infinite loop?
Dispatching an action Dispatch is just like a function which we can pass around to other components through props. You must have noticed that useReducer returns two values in an array. The first one is the state object, and the second one is a function called dispatch. This is what is used to dispatch an action.
1. useReducer() The useReducer(reducer, initialState) hook accept 2 arguments: the reducer function and the initial state. The hook then returns an array of 2 items: the current state and the dispatch function.
Can useReducer replace Redux? The useReducer hook should be used in components that have complex logic behind it. It shows as the main confusion with the Redux library, because developers tend to think that useReducer could replace the state manager library. But in fact, its use should be restricted to components.
The useReducer hook is usually recommended when the state becomes complex, with state values depending on each other or when the next state depends on the previous one. However, many times you can simply bundle your separate useState statements into one object and continue to use useState .
Just like Rostyslav said, you're not using useReducer
in the right way. This is different from the useState
hook: in that one, the parameter that it receives will serve as a default value for the defined variable within the component' state, which is why it's possible to leave it blank. When you're going to modify its contents, you just need to update its value by using the setter function returned by the hook.
However, useReducer
requires that you explicitly define the way in which the data will change according to certain actions of your choice. It's necessary for this method to receive at least two parameters: the reducer function and an initial state which will be the starting point for upcoming data transformations.
The aim of the reducer function is to perform changes on the state in response to the dispatched actions. This method will receive the current state and an action object (which is the value that you're sending via dispatch
). Depending on the received action, the reducer method must then apply some transformations in the data and return a new object that will override the current state.
The following piece of code illustrates a reducer that would work in your case. By default, the token
property of the state will be set to null
. As you can see in the code of the reducer, when an action of type UPDATE_TOKEN
is triggered, the token
property of your state will be updated to contain the token
bundled into the action payload.
const initialState = {
token: null
};
const reducer = (state, action) => {
switch (action.type) {
case "UPDATE_TOKEN":
return {
...state,
token: action.payload.token
};
default:
return state;
}
};
Since you'll be storing the token
into the store by dispatching an action, there's no need for you to use useState
to save it into a value
property beforehand. If you need to access the token from anywhere in your component, you can retrieve it directly from your appState
variable, as in the code below:
const App = () => {
const [appState, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const query = window.location.search || "";
const queryString = new URLSearchParams(query || "");
const token = queryString.get("token");
if (token) {
dispatch({
type: "UPDATE_TOKEN",
payload: { token }
});
}
}, []);
return <div className="value">Token: {appState.token}</div>;
};
The full working example can be found here.
Even though this should work, you may have to reconsider if you really need useReducer
or not. Adding reducers and actions adds complexity to the code, so it only make sense for larger data structures where more logic is required to update them. In this particular example it doesn't seems worth the effort, as the only action you need to perform is to change the value of the token
variable: since this variable is merely a string, this can be easily achieved by just using useState
.
You said that : I have a state value where initially null is stored, but after rendering the component I add a token.
if that is the case then you are doing it incorrectly .Instead of:
useEffect(() => {
const query = window.location.search || ''
const queryString = new URLSearchParams(query || '')
const token = queryString.get('token')
setValue(token)
}, [value])
it should be:
useEffect(() => {
const query = window.location.search || ''
const queryString = new URLSearchParams(query || '')
const token = queryString.get('token')
setValue(token)
}, [])
Now as soon as your component renders ,value will get updated. Now after this the below useEffect will come into picture:
useEffect(() => {
if (value && value !== null) {
// console.log(1)
dispatch({
type: 'TEST_TYPE',
payload: { data: value },
})
}
}, [value])
and dispatch will be called.
When using useReducer
hook you have to specify the reducer function and the initial state.
function reducer(state, action) {
return action.payload;
}
function YourComponent() {
const [state, dispatch] = useReducer(reducer, { data: null });
...
Then you can just utilize your state from useReducer
instead of storing duplicated data in both state from useReducer
and state from useState
useEffect(() => {
const query = window.location.search || "";
const queryString = new URLSearchParams(query || "");
const token = queryString.get("token");
if (token) {
dispatch({
type: "TEST_TYPE",
payload: { data: token },
});
}
}, []);
So to sum up, your code should resemble the following
import React, { useReducer, useEffect } from "react";
function reducer(state, action) {
return action.payload;
}
export default function YourComponent() {
const [state, dispatch] = useReducer(reducer, { data: null });
useEffect(() => {
const query = window.location.search || "";
const queryString = new URLSearchParams(query || "");
const token = queryString.get("token");
if (token) {
dispatch({
type: "TEST_TYPE",
payload: { data: token },
});
}
}, []);
return (
<div>
<pre>{JSON.stringify(state)}</pre>
</div>
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With