React - Slow keyboard input even with useMemo() - reactjs

I have this code which has plenty of inputs, one for each price value to modify it.
As the amount of inputs is high (3 prices per tarification, 3 tarifications per area), in order not to rerender everything each time, I'm using useMemo on the function that updates the value of the inputs and aside of this, I'm using useReducer to avoid having a very long code to control inputs.
However, inserting characters (or numbers in this case) in the inputs is not instant as it should be, instead it takes a short time for them to appear, not to mention consecutive inputs.
const handleUpdate = useMemo(
() => (property, valu, obid) => {
dispatch({ type: "UPDATE_DATA", property, payload: valu, id: obid });
},
[dispatch]
);
And the reducer:
function reducer(state, action) {
switch (action.type) {
...
case "UPDATE_DATA":
return {
...state,
data: state.data.map((item) => {
if (item.id === action.id) {
return { ...item, [action.property]: action.payload };
}
return item;
}),
};
}
}
I suggest checking the whole code as the problem (or solution) could be somewhere else. In order to see the whole code, you can refer to this sandcodebox link. Excuse the bad css formating as I copied just some part of it.
Note that the fetch function has been replaced by a long array simulating the data.
https://codesandbox.io/s/unruffled-feynman-g9nox2?file=/src/App.js

The point of useMemo generally is to cache the values of expensive calculations done during rendering. However, in your case, you do not have any expensive rendering calculations; you are just rendering a really large tree every time an input changes. In fact, because all your state is on the App component, you rerender the entire app every time.
The way to optimize this in React is to skip rendering components when possible. To do so, split unrelated pieces of the page into distinct components. Once you separate the logic, wrap it with React.memo(), which is a different optimization technique that can skip the rendering of a component altogether.
To me, the most obvious changes you can make are:
Move TodosDatos outside the App component because it is constant and doesn't need to be redefined on every render (which can be memory-intensive).
Move your <Table> into a new component that you memoize with React.memo(). Make sure to pass all the table's dependency values into the new component's props.
I implemented these changes here: https://codesandbox.io/s/green-breeze-mmum6n?file=/src/App.js. You should notice now that typing is nearly instantaneous. You can probably optimize it in several other places too for even better performance.

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

Tons of components are updating without any relevant change in state

I'm trying to diagnose some intermittently awful performance in my React app, and can see lots of components updating without any change in props or state.
I've got a socket connection where whenever another user is typing, it broadcasts the fact that they're typing - this is then stored in my reducer under typingUserData.
Now, pretty much every component re-renders whenever this is updated, even though they never reference this variable, nor is it included in mapStateToProps, nor do any of their children.
I can use shouldComponentUpdate to prevent updates in one component, but then every one of its children re-renders anyway. Is it intended that I should use pretty much the same shouldComponentUpdate on every component?
My understanding was that if I can prevent it higher up in the tree, lower components won't unnecessarily render, but they're doing that regardless. I guess I'm asking:
a) is there a way of finding out WHY they're choosing to re-render, and
b) is there a way of saying "don't render and none of your children should render either" in shouldComponentUpdate?
EDIT:
To add a little more context, I'm receiving typing data from my socket connection like so:
window.Echo.join('account_'+accountId).listenForWhisper('typing', e => {
dispatch({
type: TYPING_SET,
payload: e,
})
}
This is merged into my state like so:
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case TYPING_SET:
let typingUserData = cloneDeep(state.typingUserData)
typingUserData[action.payload.userId] = {
isTyping: action.payload.isTyping,
chatId: action.payload.chatId
}
return {
...state,
typingUserData
}
}
}
SOME components listen to this from mapStateToProps like so:
function mapStateToProps(state) {
return {
typingUserData: state.account.typingUserData,
}
}
...but 99% of the components don't. For whole trees that are re-rendered, nothing in the tree references typingUserData at all.
I'm using react-redux 5.0.7.
As far as I can detect, nothing is mounting twice.
Selecting 'highlight updates' in Chrome's React console tools shows pretty much everything lighting up as a result of this. Removing the action for 'typing' makes it all go away.
Hope that's enough information! Sorry for the big ol' wall of text.

React, avoid rerendering with shallowEqual

I'm starting to see performance problems and trying to optimize it.
As a first step, I'm dealing with Perf.printWasted()
That is I'm trying to eliminate unnecessary renders.
One of my component is being rerendered because of two props
a new date object.
newly created [todo] array
Suppose you are creating a calendar for todo.
For each date, I'm passing a date, and list of todos which are due that day.
I'm doing something like (simplified)
todoForDay = _.filter(todos, (todo) => {return todo.dueDate == today})
react's shallowEqual wouldn't see those two cases as equal, how should I proceed?
For #1, I could think of passing moment(date).format() as props and converting back to date object every time I pass the date.
But it would get really tiresome, because there are so many child components that needs access to the date.
Have you tried to implement the shouldComponentUpdate lifecycle method? You could check for the inequality of the passed in date prop and todos array like so:
class MyComponent extends Component {
shouldComponentUpdate(prevProps) {
const {
date,
todos,
} = this.props;
const {
date: prevDate,
todos: prevTodos,
} = prevProps;
return (
date.getTime() !== prevDate.getTime() ||
!_.isEqual(todos, prevTodos)
);
}
render() {
// render...
}
}
The _.isEqual method performs a deep equality comparison of the two todos arrays. There is also a _.isEqualWith method you could use to define your own notion of equality for those arrays if you want to be more specific.
Alternatively, you could look into something like Immutable.js as it would allow you to do an easier todos !== prevTodos comparison, but this might be overkill for your needs (depending on how much data you're working with).
If you're already doing something like this, perhaps provide some more code (your implemented shouldComponentUpdate method so we can suggest other alternatives).
For #1 you don't need to convert the prop. You can simply compare the dates with getTime() in shouldComponentUpdate():
shouldComponentUpdate(nextProps) {
return this.props.date.getTime() !== nextProps.date.getTime()
}
And for #2 unfortunately it looks like an array which contains objects, I think doing a deep equal here is more expensive than just a render.
Note that executing render() doesn't mean that the DOM will get an update. If you setup key properly then it should be fast enough. (If the todos may change its order or the newly added todo is added on top then don't use indexes as the key. Real unique keys are better in that case)
You should try to avoid unnecessary setState (if you are not using a state management library). Also try to split your components to small pieces. Instead of re-rendering a huge component every time it has an update, updating only the minimum sections of your app should be faster.
Another possibility is to re-structure your state. But it's based on your requirements. If you don't need the full datetime of each todo, you can group your state something like:
todos: {
'2017-04-28': ['Watch movie', 'Jogging'],
'2017-04-29': ['Buy milk']
}
By doing this you don't even need the filter. You can grab the todos of the date your want easily.
In a more complex case which you need more information, you can try to normalize your state, for example:
{
todos: {
1: { text: 'Watch movie', completed: true, addedTime: 1493476371925 },
2: { text: 'Jogging', completed: true, addedTime: xxxxxxxxxx},
3: { text: 'Buy milk', completed: false, addedTime: xxxxxxxxxx}
},
byDate: {
'2017-04-28': [1, 2],
'2017-04-29': [3]
}
}
Now if add a new todo to the todos, it won't affect your component which is referring to byDate so you can make sure that there is no unnecessary re-renders.
I'm sharing my solutions.
For calendar based todo list, I wanted to avoid implementing shouldComponentUpdate for every subcomponents for a calendar day.
So I looked for a way to cache the dates I created for the calendar. (Unless you change the month, you see the same range of dates) .
So https://github.com/reactjs/reselect was a great fit.
I solved #2 with the reselect as well.
It memoizes (caches) function result until function params change.

reselect memoization ignore irrelevant updates

Following the example on the reselect docs:
import { createSelector } from 'reselect'
const shopItemsSelector = state => state.shop.items
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
In a typical redux app, subtotalSelector will recompute if a user updates item.name, even though this has no impact on the result. Is there a way to avoid this?
Two solutions:
Let it be. Unless you have a large number of items, the browser's computing capacity is well enough to handle the recompute.
Seperate prices from the item object. That is, you have state.shop.items.itemNames(containing id-name pairs) and state.shop.items.itemValues(containing id-value pairs). Then only the itemValues is passed to the selector.
I have a similar problem and I have found a sort of hack to get arround it.
I have a complex set of filters, and a huge number of items to filter through. Part of the filter state includes display state. I want to ignore changes in the display state so I don't filter a huge list all the time. This is an easy-ish solution:
const getFilters = createSelector(
state => state.filters,
filters => {
const filtersWithoutDisplay = {};
const ignoreObj = { collapsed: null };
for (let filterGroup in filters) {
filtersWithoutDisplay[filterGroup] = Object.assign({}, filters[filterGroup], ignoreObj);
}
// We create a new object every time, so this cannot be memoized properly unless we stringify.
return JSON.stringify(filtersWithoutDisplay);
}
);
It returns a JSON string that has to be parsed, but it's a primitive so as an input to another selector it doesn't trigger a recomputation if the actual contents don't change. That's kind of a hack.
You could also define an object outside of the selector function and always keep the same reference, change the insides according to this same patter, and then use a custom deep equality check by pulling in createSelectorCreator, as explained here https://github.com/reactjs/reselect#q-why-is-my-selector-recomputing-when-the-input-state-stays-the-same . This is probably a better way to go, but as it says:
Always check that the cost of an alternative equalityCheck function or deep equality check in the state update function is not greater than the cost of recomputing every time.
That goes for the JSON.stringify hack as well. I wouldn't do it for the huge list, but for the filters, sure.
In my situation, it's probably better to refactor my state because the filter values may be a separate concern from the filter display settings, and this may not be the only time I want them separate.

Resources