useReducer - how to tell when state has been updated - reactjs

When using useReducer to manage state, the internal state update is deferred.
I.e. if I were to initialize a reducer with something like
const [state, dispatch] = useReducer(reducer, {prop: ""})
... then later call a dispatch like so ...
dispatch({
type: 'ACTION_THAT_UPDATES_PROP'
});
console.log(state.prop)
... the value of "state.prop" wouldn't contain the new value, due to the deferred state update.
Is there a way to get the newly updated state? In other words, I'm looking for a similar mechanism provided by React's setState method, where you could do something like
this.setState({
propName: "newPropValue"
}, () => {
// state.propName would have value of "newPropValue" here
})

You can access it, but not in the body of the function nor directly after the dispatch, since the state needs to be updated first. React's state and lifecycle mandates that the new state won't be approachable in the context of the current state.
The way to do so is to use useEffect:
React.useEffect(() => {
console.log(state.prop)
}, [state.prop]);

Related

Can I await the update function of the useState hook

I am building an app to understand the useState hook. This app simply has a form for entering username. I am trying to save the entered username. So, I have used react useState. And I tried to await the updating function of the useState in the event handler.
const usernameChangeHandler = async (event) => {
await setEnteredUsername(event.target.value);
console.log(enteredUsername, enteredAge);
};
And when I tried to log the username it doesn't show us the current state but the previous state. Why?
const usernameChangeHandler = async (event) => {
await setEnteredUsername(event.target.value);
console.log(enteredUsername, enteredAge);
};
enteredUsername is never going to change. It's a closure variable that's local to this single time you rendered the component. It's usually a const, but even if it was made with let, setEnteredUsername does not even attempt to change its value. What setEnteredUsername does is ask react to rerender the component. When the render eventually happens, a new local variable will be created with the new value, but code from your old render has no access to that.
If you need to run some code after calling setEnteredUsername, but you don't actually care if the component has rerendered yet, the just use the value in event.target.value, since you know that's going to be the new value of the state:
const usernameChangeHandler = (event) => {
setEnteredUsername(event.target.value);
console.log(event.target.value, enteredAge);
}
If instead you need to make make sure that the component has rerendered and then do something after that, you can put your code in a useEffect. Effects run after rendering, and you can use the dependency array to make it only run if the values you care about have changed:
const [enteredUsername, setEnteredUsername] = useState('');
useEffect(() => {
console.log('rendering complete, with new username', enteredUsername);
}, [enteredUsername]);
const usernameChangeHandler = (event) => {
setEnteredUsername(event.target.value);
};
the act of setting state is asynchronous; therefore, console logging directly after setting your state will not accurately provide you with how state currently looks. Instead as many have suggested you can utilize the useEffect lifecycle hook to listen for changes in your enteredUserName state like so:
useEffect(() => {
console.log(enteredUsername);
}, [enteredUsername]);
listening for changes within the useEffect will allow you to create side effects once state has updated and caused your component to rerender. This in turn will trigger your useEffect with the enteredUsername dependency, as the enteredUserName state has changed.

About useSelector equality check

I have a question about redux useSelector equality check.
Refer to the React Redux Hook document (https://react-redux.js.org/api/hooks#equality-comparisons-and-updates)
useSelector will do reference comparison to the return value and the previous value, force re-render if result appears to be different
I have a default blog store state like this
searcherParam: {
keyword: '',
categories: [],
tags: [],
},
In the component, I use useSelector to retrieve the value
const searchParam = useSelector(state => state.Blog.searcherParam)
If I dispatch an action to update searcherParam with same value, the component will re-render because the return value is object (shallow compare)
So I retrieve the value by calling useSelector multiple times
const keyword = useSelector(state => state.Blog.searchrParam.keyword)
const categories = useSelector(state => state.Blog.searchrParam.categories)
const tags = useSelector(state => state.Blog.searchrParam.tags)
And I dispatch an action to update searcherParam with same value again
the component will not re-render
The point I can't understand is why component doesn't re-render ?
If useSelector do reference comparison, the categories value (array) should not be the same reference and the tags as well after dispatching
Do I have any misunderstanding ? Thanks
The reason about not re-rendering is because I save the categories (from redux store) via useState.
and use the useState's value to dispatch, so it is same reference...
here is the codesandbox, sorry for stupid question Q_Q
https://codesandbox.io/s/useselector-test-u6pvg
So when you do this :
const newKeyword = useSelector(state => state.Blog.searchrParam.keyword)
const newCategories = useSelector(state => state.Blog.searchrParam.categories)
const newTags = useSelector(state => state.Blog.searchrParam.tags)
This simply means that, you would want to have the latest value of each of them.
It does not translate to this :
searcherParam: {
keyword: newKeyword,
categories: newCategories,
tags: newTags,
},
Because searcherParam will always be a new reference when the action is dispatched.
If you just want to re-render, if any of the 3 properties change, then you can simply fetch state.searcherParam and dispatch and achieve a re-render.
Fetching individual properties help when you would want to re-render only on keyword change. You do not want to re-render when either of cateogories or tags have changed.
Note in this case if you just fetch searcherParam, it would not care which of 3 properties have changed, you will get a re-render because it is a new reference.
This is what the docs have mentioned.
After discussion in comments.
Made a trivial implementation. Check below
Rather than selecting the above properties separately, they all can be obtained via deconstructing the searchrParam object:
const { newKeyword, newCategories, newTags } = useSelector(
state => state.Blog.searchrParam
)
Since you trying to add same value to the store and also you are referring directly from the store (state.Blog.searchParam), your code below will not cause re-render.
const searchParam = useSelector(state => state.Blog.searcherParam)
In order to make it to re-render, you can try below
const searchParam = useSelector(state => ({keyword: state.keyword, categories: state.categories, tags: state.tags}))
The above code will always try to re-render, since the returned object reference will not be same.

What is the right way to update the state instantly?

Sorry if someone has already answered this question, but I didn't find what I am looking for.
I recently started learning react and notice that there are quite a few ways to set the state. For example, I have a counter in an object, and I want to increase it.
const [state, setState] = React.useState({ counter: 0 });
And all the functions below give the same result, but as I understood, they do it asynchronously.
setState({ ...state, counter: counter + 1 }):
setState(() => ({ ...state, counter: counter + 1 }));
setState(prevState => ({...prevState, counter: counter + 1 }));
setState(counter = counter + 1);
How can I update the state instantly and properly after calling the setState function? Thank you in advance!
In order to do some logic with the newly updated value of the state, you should use
The UseEffect Hook
useEffect(() => {
/* use the new state value here */
}, [state])
or, [if you're using component classes], the callback function
this.setState({ ...state, counter: counter++ },
() => { /* use the new state value here */ }
);
Also for a simple counter and in order not to be confused betweeen react components and react hooks... I would recommend using the useState like this :
const [counter, setCounter] = React.useState(0);
setCounter(counter++);
What do you mean by
How can I update the state instantly and properly after calling the setState function?
?
You actually are updating state in the setState function, so the question itself sound strangely.
If you want to perform some action synchronously right after new value will be set, you can use callback function, which was mentioned by Seba99. Here are link to docs (basically it's setState docs). Optional callback is executed after state update, and this.state inside it will always be up-to-date with latest changes, you've made in setState.
So, if you need to synchronously get latest state and perform some actions with it (even update state one more time) - use callback of this.setState.
How can I update the state instantly
You can't, because you've already declared your state with const, right?
const /*const*/ [state, setState] = React.useState({ counter: 0 });
function handleClick() {
setState(anything)
// you declared state with const, you "obviously" shouldn't expect this
// to "somehow" immediately change `state` to anything
}
You can only make it work as expected even when state's updated asynchronously, or not instantly, depends on circumstances. If you want to get the newly updated value of your state to use later in a consequence, cache that new value to use is the right way. For example:
const newStateA = changeState(stateA)
setStateA(newStateA)
setStateB(calculateBFromA(newStateA))
// instead of
setStateB(calculateBFromA(stateA)) // will get the old value of stateA
Of course, you could just set state = something if you haven't declared it with const, like let or var instead, it would change state instantly, but it wouldn't tell React to rerender the component later with the newly updated state value, unless you just set it with with the "second array-destructured" param setState (talking about Hooks), and btw this (declare state with let/var`) is obviously the wrong way

Setting Redux state as a default state when using React Hooks

I have a redux action that fetches all data and stores it into a global Redux store.
I want to store that state in a local state using Hooks so that the actual state doesn't get changed when I change it locally.
What I am doing right now is,
const [filteredTable, setFilteredTable] = useState([])
useEffect(() => {
props.fetchDatabase();
props.fetchOptions();
setFilteredTable(props.filtered_table_data);
}, [])
In useEffect, the props.fetchDatabase() gets the props.filtered_table_data and I can see that when I console.log it out.
However, when I use Hooks to store it into a local state and check if it's in there,
console.log(filteredTable, 'filteredTable')
just gives me [].
What am I doing wrong?
I believe the props.fetchDatabase() call is asynchronous, so by the time you are attempting to setFilteredTable the props.filtered_table_data has not updated yet.
You can try something like this:
useEffect(() => {
props.fetchDatabase();
props.fetchOptions();
}, [])
useEffect(() => {
setFilteredTable(props.filtered_table_data);
}, [props.filtered_table_data]);
Note that this effect will run every time filtered_table_data changes, so you may need to wrap around the code in the callback with some sort of condition if you want to restrict setting the local state.
useEffect's callback with [] as hook's second argument is only being called once when component just mounted. Inside it fetchDatabase, and fetchOptions callbacks are called, and right after that (when data isn't yet fetched) you call setFilteredTable, that's why there are empty array occurs in filteredTable.
Not sure if this answers your question, but React-Redux provides some hooks for accessing the redux store.
The one that might be of interest to you is the useSelector() hook.
Here's an example usage:
import { useSelector } from 'react-redux';
const App = () => {
const tableData = useSelector(state => state.tableData);
...
}

useReducer: dispatch causes rerender even if state is not changed

I noticed that if I dispatch an action that happens to not to modify the state, the component is re-rendered anyway.
Example:
// for simplicity sake, we simply pass state on (no mutation)
const someReducer = (state, action) => state
const App = () => {
const [state, dispatch] = useReducer(someReducer, 0)
// in my use case, I want to dispatch an action if some specific condition in state occurs
if(state === 0) {
dispatch({ type: 'SOME ACTION' }) // type doesn't matter
}
// return some JSX
}
I get:
Error in app.js (16929:26)
Too many re-renders. React limits the number of renders to prevent an infinite loop.
Is this by design? Should it be this way?
In terms of your example as-is, it's not immediately obvious as to what is causing the component to re-render. However, the docs seem to suggest that it's not a guarantee that a re-render won't occur when you don't mutate the state:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
This is why it's generally recommended that you run code that potentially has side effects in useEffect e.g.
const [state, dispatch] = useReducer(someReducer, 0);
...
useEffect(() => {
if (state === 0) {
dispatch({ type: 'SOME ACTION' });
}
}, [state]);

Resources