Alternative way to handle state in React using memo - reactjs

When it comes to handling complex state in React everybody suggests to flatten the state to avoid something like this just to update a single property:
setState({ …state, user:{ …state.user, profile:{…state.user.profile, address:{…state.user.profile.address, city:’Newyork’}} }});
Which is really cubersome to work with. There is another way: use an object holding your state and return that from a memoized function. Then whenever you made a change simply force a re-render.
// note the reference cannot be changed, but values can.
const data = useMemo(() => ({
user: {
name: "",
profile: {
address: {
city: "New york"
}
}
}
}), []);
// use dummy data to trigger an update
const [toggle, setToggle] = useState(false);
function forceUpdate() {
setToggle(prev => !prev);
}
function makeChanges() {
// make any change on data without any copying.
data.user.address.city = "new city name";
// hydrate the changes to the view when you're done
forceUpdate();
}
return (
<div onClick={() => makeChanges()}>{data.user.address.city }</div>
)
Which works perfectly. Even with massive and complex data structures.
From what I can tell state is really just a memoized values which will trigger an update upon change.
So, my one question: What is the downside of using this?

The docs say useMemo is not a guarantee:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values [...]
If you'd really want to do something like this and you are absolutely positively sure you're willing to do things unlike anyone else in React land, you'd use useRef for state storage that doesn't cause rerenders by itself. I'm not going to add an example of that, because I don't recommend it in the least.
You should also note that your method will not cause memoized (React.memo()) components to rerender, since they will not "see" changes to props if their identity does not change. Similarly, if another component uses one of your internally mutated objects as a dependency for e.g. an effect, those effects will not fire. Finding bugs caused by that will be spectacularly annoying.
If modifying deep object structures is otherwise cumbersome, see e.g. the Immer library, which does Proxy magic internally to let you modify deep objects without trouble – or maybe immutability-helper if you're feeling more old-school.

Related

How should I use selectors in Redux Toolkit?

I am learning Redux Toolkit. From a React POV it seems very intuitive to access whatever part of the state you need from within useSelector using an inline arrow function, and then conduct any calculations. As an example consider a cart item with its data (like item count) in redux store.
function CartItemCounter({ itemId }){
const cart = useSelector(state => state.cart);
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
return <div>{itemId} - {count} nos</div>
}
But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all. What is the right way, and why is it better?
The information that is out there is essentially talking about different levels of optimization while using useSelector.
What you need to understand before anything else, is how useSelector works internally.
How does useSelector work?
When you pass a function to useSelector (obviously inside a react component), it essentially hooks on to the global redux state. Whenever any change happens in any part of the global state (i.e. when dispatch() is called from any part of the app), redux will run all the functions you passed to useSelector in your app, and perform certain checks.
Redux will take the result from each function, and compare it to the value it got the last time it ran the same function.
How does it make this comparison?
It uses a reference equality for this comparison. So if redux has to think that the result of the function hasn't changed, either the value returned from the function has to be a primitive and equal.
4 === 4 // true
'itemA' === 'itemA // true
Or, the value returned must be a derived data type (arrays, objects), with the same reference. So essentially the same object.
const x = { name: 'Shashi' }
const fn1 = () => x;
const fn2 = () => x;
const fn3 = () => { name: 'Shashi' }
fn1() === fn2(); // true
fn1() === fn3(); // false, because the objects are different, with different references
In practice, redux changes the wrapping object if either a key (or key of a nested object) is changed, or you manually change the entire object using a dispatch action (This is related to the immer library integration). This is similar to how you would do in regular React.
/* See how most keys are spread in, and will hence maintain reference equality.
While certain keys like 'first', 'first.second', 'first.second[action.someId]'
are changed with new objects, and so will break reference equality */
function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}
Otherwise it maintains the same objects within its state, and returns the exact same objects with the same references, when you access them. To verify this, if you access your cart twice, its literally going to be the same object.
const cart1 = useSelector(state => state.cart)
const cart2 = useSelector(state => state.cart)
cart1 === cart2; // true
What does it do with this comparison?
If the comparison returns true, i.e. the new value is the same as the old value, Redux tells that instance of useSelector to chill tf out, and not do anything. If it returns false however, it tells that component to re-render. After all, the value you are accessing from state has "changed"(according to Redux laws), so you probably want to show the new value.
With this information, we can make change the kind of function we pass to the useSelector, in order to get certain optimization benefits.
Optimization Levels
Level 0: Accessing slice data inline
const cart = useSelector(state => state.cart)
// extract the information you need from within the cart
const itemInCart = cart.items[itemId];
const count = itemInCart?.count || 0;
This is not a good way to access the data. You actually need a subset of the data from the cart, but you are fetching the whole thing, and doing the calculation outside the selector.
Problems:
When you put stuff like this inline, what happens if you change the shape of your data in the future? You have to go to every place that uses useSelector and manually change it. Not so good.
More importantly, every time any part of the cart changes, the entire cart object actually changes. So Redux sees your component that asks for the cart, and thinks
The cart has changed. This component is asking for the cart. It should probably re-render.
BAM Every single instance of this component rerenders. And for what? The count of the item you're referencing probably didn't change. So ideally there shouldn't have been a re-render.
Level 1: Centralize the selector
An easy optimization is to put the selector function in a centralized location next to your slice. That way, if your data shape changes in the future, you can just change it in one place, and your whole app (wherever it uses that data) will work with the new shape.
// inside or next to the slice file
const selectCart = (state) => state.cart
//...
// somewhere inside a react component
const cart = useSelector(selectCart)
Level 2: Access the relevant data
Since redux is comparing the results of your selector function, if you want to avoid unnecessary rerenders, you want to make sure the results have reference equality (===). So target the exact value you wish to look at, in your selector.
// extract the information you need from within the cart, *within the selector*
const count = useSelector(state => state.cart.items[itemId]?.count || 0)
// You don't have to use a one-liner, a multi-line function is better for readability
When Redux executes these functions, it keeps a record of the value returned from these selector functions, for each individual useSelector. This time the values are going to be the same for every single counter, except the one that actually changed. All those other counters that didn't actually change in value don't have to unnecessarily re-render anymore!
And if any of you folks think this is premature optimization, the answer's no. This is more along the lines of putting a dependancy array on your useEffects to avoid infinite loops.
Not forgetting the Level 1 optimization, we can also extract this function centrally
const selectItemById = (state, itemId) => (state.cart.items[itemId]?.count || 0);
function CartItemCounter({ itemId }){
//...
// somewhere inside a react component
const count = useSelector((state) => selectCart(state, itemId))
//...
}
So that solves all of our problems right?
For now, yes. But what if this selector function has to run some expensive computation.
const selectSomething = (state) => reallyExpensiveFn(state.cart)
//...
// somewhere inside a react component
const cart = useSelector(selectSomething)
You don't want to keep running that do you?
Or what if you have no option but to return new objects from your select function. A common scenario for this case would be returning a subset of data from the state.
const selectFilteredItems = (state) => state.itemsArray.filter(checkCondition) // the filter method will always return a new array
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders every time
To solve this you would have to memoize or cache the results from the function call. Essentially you would need to make sure that if the input arguments are the same, the result will maintain reference equality with the previous result. This introduces the need to maintain some kind of cache state.
Level 3: createSelector
Fortunately, Reselect library, which is reexported with Redux Tookit, does this work for you. You can take a look at the redux toolkit for the syntax.
const selectFilteredItems = createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const cart = useSelector(selectFilteredItems) // re-renders only when needed
Here the second function is called the transformation function, and is where we put the expensive computation, or the function that returns inconsistent references as a result (filter,map etc).
The createSelector caches
a) the arguments to the transformation function
b) the result of the transformation function
of the previous call to the selectFilteredItems function. If the arguments are the same, it skips executing the transformation function, and gives you the result you got the last time it was executed.
So when useSelector looks at the result, it gets reference equality. Hence the re-render is skipped!
One little caveat here is that createSelector only caches the very previous result. This makes sense if you think about a single component. In a single component you are only concerned about differences in values and results compared to the previous render. But in practice, you are likely to share selectors across multiple components. If this happens, you have a single cache location, and multiple components using this cache. i.e. Stuff breaks.
Level 4: createSelector factory function
Since the logic for your selector is the same, what you need to do is run createSelector for each component that uses it. This creates a cache for each component, giving us the desired behaviour. In order to do this, we use a factory function.
const makeSelectFilteredItems = () => createSelector(
(state) => state.itemsArray, // the first argument accesses relevant data from global state
(itemsArray) => itemsArray.filter(checkCondition) // the second parameter conducts the transformation
)
//...
// somewhere inside a react component
const selectFilteredItems = useMemo(makeSelectFilteredItems,[]); // make a new selector for each component, when it mounts
const cart = useSelector(selectFilteredItems) // re-renders only when needed
You intend to make a new selector (and by extension, cache) for each new component that mounts. So you put it inside the actual component function and not on the module scope. But this will re-run makeFilteredSelector for each render, and hence create a new selector for each render, and hence eliminate the cache. This is why you need to wrap the function in a useMemo with an empty dependency array. It runs on every mount.
And voila!
You now know where, why and how to use selectors in Redux. I personally feel that the createSelector syntaxes are slightly contrived. There is some discussion on changing cache sizes going on. But for now I feel that sticking to the docs should get you through most situations.
But I'm seeing all this information saying you should define your selectors beside your slice, use createSelector and all.
That's the way to go if you're deriving something from the state, which ends up being an expensive computation or something that's reused often throughout your app. Imagine, for example, your state.cart can contain 50.000 items and you need to sort them from most expensive item to least expensive. You don't want to re-calculate this all the time because it slows your app down. So you cache/memoize the result.
What is the right way, and why is it better?
The right way is to use memoization helpers like createSelector when/if you want to avoid expensive computation. Most people optimize prematurely, so I'd just stick to useSelector and keep it simple if in doubt.

How to use an array as a dependency of a React hook

I have a component that has a callback. It depends on an array of plain old objects stored in redux which won't change very often while the component itself will change its state pretty frequently. Some subcomponents should be rerendered on those state changes, but the one that uses the callback, should not.
What's the best approach to making an array a dependency of useCallback()? So far, I've been using
const handleAllItemsSelectedChange = useCallback(
checked => {
if (checked) {
dispatch(setSelected(items));
} else {
dispatch(selectSelected([]));
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(items)]
);
This doesn't seem ideal, and potentially slower than just rerendering the component every time. I can't imagine this isn't a very common use-case. The React team surely has a best practice for this, right? I can't find it in the documentation.
JSON.stringify or any deep comparison is going to be inefficient and slow. React has no plans to support it
Depending on whether you add or remove items (if not mutating the objects) you can just compare with items.length. Or you could possibly save performance by just creating the function each time, as opposed to trying to save performance putting it in a useCallback.
It's a case by case scenario
In redux reducer every time the array changes you have to create new array. So, your array becomes immutable and you can use it for dependency by reference. Example below just demonstrates the principle.
function reducer(state, action) {
switch(action.type) {
case "addItem":
return {...state, items: [...state.items, action.value]};
case "changeProp":
return {...state, prop: action.value}
default:
return state;
}
}
As you can see every time the array changes you'll get the new instance of the array. That means you can use it by reference and don't need to strignify it anymore:
const handleAction = useCallback(checked=>{
....
}, [items]);
By the way immutability is the approach recommended by redux documentation

Best practice for indicating prop is used in useEffect or similar

Often when writing a component in React+Typescript, I want to trigger a useEffect hook on one of the props. For example, consider a button that executes a different function for each consecutive mouse click:
export function ActionButton({clickActions}: {clickActions: (() => void)[]}) {
const [clicked, setClicked] = useState(0);
useEffect(() => {
if (clickActions.length > 0 && clickActions[clicked % clickActions.length]) {
clickActions[clicked % clickActions.length]();
}
}, [clicked, clickActions])
return <button onClick={() => setClicked(clicked => clicked + 1)}/>
}
In this case, clients of this component, need to be aware that they somehow need to prevent clickActions from being a different instance on every render. For example, it could simply be a constant, or be memoized by using useMemo.
Is there a best practice for making my clients aware of this? Are there ways to trigger compile time errors when this rule is violated?
NOTE: I realize this particular case can be solved without using useEffect, but it's just a simple example to illustrate the pattern. I'm not interested in solving this particular problem, but in how to solve the general problem.
There's nothing you can do by way of TypeScript trickery to enforce that clickActions can't change every time the parent renders.
However, you can simply remove clickActions from useEffect’s dependency list. You have to be careful, in general, when doing this, but in this case, it's safe, because the callback you’re passing to useEffect synchronously executes an action when clicked changes, which means the callback will have a reference to the most recent clickActions when it needs it.
This is analogous to the sample operator in RxJS; i.e. clickActions is sampled by clicked.
useEffect(() => {
if (clickActions.length > 0 && clickActions[clicked % clickActions.length]) {
clickActions[clicked]();
}
}, [clicked])
Please also note that even when the parent uses useMemo, it's not guaranteed that it won’t recreate clickActions at a time of React’s choosing:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
(see useMemo docs)
It's safe to assume useCallback comes with a similar caveat, although it's not specifically mentioned in the docs.
generally if you have a function that is being passed around its important (especially if its 'created' within a function) to use useCallback when creating it. This ensures that the "props don't change" and thus preventing a rerender.
parentFunction = useCallback(()=>{
if (specialVar==='dance'){
return () => {
console.log('dance')
}
}
else {
return () => {
console.log('do the boogy')
}
}
}, [specialVar])

How to make State update when only a string array is update

Guys i have this example code bellow:
const [data, setData] = useState([{question: '', option: ['']}]);
Then data and setData will pass to my component, like:
<Question setData={setData} data={data}/>
My code inside Question component is:
const handleAddOption = (questionIndex: number) => {
let newArray = data;
newArray.map((item: any, i: number) => {
if (i === questionIndex) {
newArray[questionIndex].options.push('');
}
});
setData(newArray);
};
The problem is, if i add a new entire Object it will "refresh" my page and show, but, when i add like the last lines of code, only a new string inside the array, it will not "re-render".
Anyone knows how to solve this?
In react first rule of thumb is don't mutate state directly. It works for class-based components and setState, it works for redux's reducers, it works for hook-based useState too.
You need to
setData((data) => data.map((item, index) => {
if (index !== questionIndex) return item;
return {
...item,
options: [
...item.options,
''
]
};
}));
There are several items to highlight here:
as well as for class-based component's setState there is possible to provide callback into updater function. I better skip describing it in details here. May suggest to take a look into SO question and ReactJS docs. Yes, it's not about hooks but it uses the same logic.
We need to merge our old data with changes to keep properties we don't want to change still present. That's all this spread operator is so hardly used. Take a look into article on handling arrays in state for React
Last but not least, we have check to directly return item if it's not we want to update without any change. This makes us sure no related children components will re-render with actually exact the same data. You may find additional information by searching for query "referential equality"(here is one of article you may find).
PS it may look much easier to force update instead of rewriting code completely. But mutating state directly typically later ends up with different hard-to-reproduce bugs.
Also you may want to change components hierarchy and data structure. So no component would need to traverse whole array to update single nested property.
It seems like You have typo write newArray[questionIndex].option.push(''); instead of newArray[questionIndex].options.push('');
But if it doesn't help try forceUpdate(); more details You can find in this answer How can I force component to re-render with hooks in React? or try to use this package https://github.com/CharlesStover/use-force-update
Good Luck :)
Rough implementation
I think you can change:
const [data, setData] = useState([{question: '', option: ['']}]);
// into
const [questions, setQuestions] = useState([]);
And as you receive new question objects, you can do setQuestions([...questions, newQuestion]).
That is, assuming, you have a form that is receiving inputs for new questions. If this is the case, in your onSubmit function for your form, you can generate your new question object and then do setQuestions([...questions, newQuestion]).

Ramifications of React setState directly modifying prevState?

I was looking at some example react code (in the antd docs), and I noticed they have code that is equivalent to:
this.setState(prevState => { prevState.name = "NewValue"; return prevState; });
This looks a bit naughty, but does it actually break anything? Since it's using the arrow function it's not breaking the ordering of changes being applied even if React batches them up in the background.
Of course setState is intended to expect a partial state so there might be performance side effects there as it might try to apply the whole state to itself.
edit: (in response to #Sulthan)
The actual code is this:
handleChange(key, index, value) {
const { data } = this.state;
data[index][key].value = value;
this.setState({ data });
}
n.b. data is an array, so its just being copied by reference then mutated.
It's actually completely wrong as its not even using the arrow function to get the latest state.
It comes from the editable table example here: https://ant.design/components/table/
Your example can be also rewritten as:
this.setState(prevState => {
prevState.name = "NewValue"
return "NewValue";
});
When a function is passed to the state the important thing is not to mutate the passed parameter and return the new state. Your example fails both.
...prevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new state object based on the input from prevState...
(from setState)
I am not sure whether it was ever possible to use setState like in your example but looking into the change log I really doubt it.

Resources