onCallback React Hook - best practice query - reactjs

This is more of a best practice question than anything. But I'm wondering if any inline functions within a React component should typically be wrapped with an onCallback for performance? Under what circumstances would I not wrap a function like this with onCallback?
For example:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = () => {
alert('do something');
}
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
In this example should I be doing this:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = useCallback(() => {
alert('do something');
},[]);
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};

I will try to explain the useCallBack with an example. We all know the definition of useCallBack, but when to use is the trick part here. So, let me take an example.
const RenderText = React.memo(({ text }) => {
console.log(`Render ${text}`);
return (<div>{text}</div>);
});
const App = () => {
const [count, updateCount] = React.useState(0);
const [lists, updateLists] = React.useState(["list 1"]);
const addListItem = () => {
updateLists([...lists, "random"]);
};
return (
<div>
{`Current Count is ${count}`}
<button onClick={() => updateCount((prev) => prev + 1)}>Update Count</button>
{lists.map((item: string, index: number) => (
<RenderText key={index} text={item} />
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
jsFiddle: https://jsfiddle.net/enyctuLp/1/
In the above, there are two states.
count - Number
lists - Array
The RenderText just renders the text passed to it. (which is the item in the list). If you click on the Update Count button, the RenderText will not re-render because it is independent from the main component (App) and during the updateCount, only the App component will re-render, since it needs to update the count value.
Now, pass the addListItem into RenderText component and click on the Update Count button and see what happens.
jsFiddle: https://jsfiddle.net/enyctuLp/2/
You can see that the RenderText will re-render even though there is no change in the list array and this is BECAUSE :
when the count is updated, the App will re-render
which, will re-render the addListItem
which causes the RenderText to re-render.
To avoid this, we should use useCallBack hook.
jsFiddle: https://jsfiddle.net/enyctuLp/4/
Now, the addListItem function has been memoized and it will only change when the dependency are changed.

ToolSearchCollapse is your child component, and so wrapping your handleClick function makes sense. Coz every time the parent component re-renders a new reference of handleclick will be passed to ToolSearchCollapse, and your child will re-render every time a parent state changes, (from states that aren't passed to the child also). Using useCallback will not allow creating new references, and thus you can control when your child should render.
If you don't pass this fn to the child, I don't see any reason to use useCallback.

Related

React hooks: functoin, callback memo

I was reading how to use react hooks, callback, and memo adequately. It was mentioned on one of the post that a bad implementation will cost more vs not using useMemo
We started implementing our code to exactly separate the code for UI logic and for the view only
for example, we created a custom hook that handles the UI logic
controller
const useController = () {
const [click, setClick] = useState(0)
const refresh = () => {
setClick(0)
}
const onClick = () => {
setClick(click + 1)
}
return {
click, onClick, resfresh
}
}
component
const Component = () => {
const {click, onClick, resfresh} = useController()
return(
<div>
<label onClick={onClick}>{click}</label>
<button onClick={refresh}>refresh</button>
</div>
)
}
My question here is whether the functions inside the hook are as well recreated when the component is re-rendered. If so, should it be ok to wrap a function inside a custom hook with useCallback or useMemo if there's an actual need?
Yes, you're creating new handler functions every time Component rerenders - but if the only children of the component are built-in JSX elements, it'll have no noticeable effect at all.
If you wanted to return a stable reference to the functions - which would be a good practice but not necessary - yes, it'd be perfectly fine (and expected) to use useCallback or useMemo.
const useController = () {
const [click, setClick] = useState(0);
const refresh = useCallback(() => {
setClick(0);
}, []);
const onClick = useCallback(() => {
setClick(click => click + 1);
}, []);
return {
click, onClick, refresh // make sure to spell this right
};
};
Whether useMemo or useCallback are used would matter more if you had other React components who were children of the Component component use them.
const Component = () => {
const {click, onClick, resfresh} = useController();
return(
<SomeOtherComponent {...{ click, onClick, refresh }} /> // make sure to spell this right
);
};
Making sure the references are as stable as possible can make thing easier performance-wise (...in unusual situations) and can also make their uses easier, especially in conjunction with the common exhaustive-deps linting rule.

How to pass data from child component to parent component without the child component rerendering in React?

My child component is an accordion with controls in it. The problem is, everytime you input data on the controls and it triggers an onChange event which passes its data to its parent component, the accordion closes/collapses and I don't need that behavior.
The accordion is just imported from a library and I can't change its behavior.
Is there anyway to prevent this or pass props/data from child component to parent component without rerendering/restarting DOM of child component?
Parent component:
const ParentComponent = () => {
const handleChangeChildAccordion = (e) => {
console.log(e);
}
return (
<ChildComponentAccordion currentData={currentData} onChangeTPAccordion={handleChangeChildAccordion}/> }
);
}
Child Component:
const ChildComponent = (props) => {
const onDataChangeHandler = (e) => {
props.onChangeTPAccordion(e);
};
return (
<Accordion onChange={onDataChangeHandler}/>
);
}
The child component doesn't remount, it rerenders. Normally, a component rerenders any time any its parent rerenders. To avoid that you:
Avoid changing the child component's props, and
Memoize the child component so it doesn't rerender when its props don't change in a way it cares about.
You do the first by ensuring that, for instance, callbacks you pass the component are stable (they don't change on every render). In a function component, you do it by using useMemo, useCallback, and/or useRef. In a class component, you typically do that by making the callbacks properties on the component instance that aren't recreated.
You do the second by using React.memo (if it's a function component) or for a class component by extending PureComponent (for simple memoization) or implementing shouldComponentUpdate.
You haven't given us much code to work with, so here's a simple example:
const { useState, useCallback, useRef } = React;
const log = (...msgs) => console.log(...msgs);
const Child1 = React.memo(({value, doUpdate}) => {
log(`Child1 rendering`);
return <div>
Child1 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Child2 = React.memo(({value, doUpdate}) => {
log(`Child2 rendering`);
return <div>
Child2 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Parent = () => {
log(`Parent rendering`);
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
// `useCallback` remembers the first function you pass it and returns
// it to you, only returning the new one you pass it if the
// dependencies array changes. This is one way to create a stable
// callback.
const update1 = useCallback(() => setCounter1(c => c + 1), []);
// `useRef` gives you an object you can put arbitrary properties on,
// which is another way to have a stable callback.
const update2Ref = useRef(null);
if (!update2Ref.current) {
update2Ref.current = () => setCounter2(c => c + 1);
}
const update2 = update2Ref.current;
return <div>
<Child1 value={counter1} doUpdate={update1} />
<Child2 value={counter2} doUpdate={update2} />
</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
TypeScript playground with types added
That shows two different ways of making acallback stable (useCallback and useRef). (useMemo is what useCallback uses under the hood, so I didn't do an example of it.)
In that example, notice how the child telling the parent to update its counter doesn't cause the other child to re-render. That's because:
We don't change the other child's props (because our callbacks are stable and the other child's counter didn't change), and
We've memoized the child components
You can customize whether the component re-renders by passing a second argument to React.memo which is a function that says whether the new props coming in are "the same" (for rendering purposes) as the previous ones. By default, React.memo does a simple === comparison on each prop. And again, for class components, PureComponent does the simple === comparison, or you can implement shouldComponentUpdate yourself for more finely-tuned checks.

React access state after render with functional components

I'm a bit of a newbie with React functional components, I have a child and parent components with some state that gets updated with useEffect, which state apparently resets back to its initial values after render.
Parent has a list of users it passes to its child:
Parent:
const Parent = () => {
const [users, setUsers] = useState([])
const getUsers = () => {
setUsers(["pedro", "juan"])
}
useEffect(() => {
getUsers()
}, []);
return <div>
<Child users={users} />
}
Child:
const Child = () => {
const [users, setUsers] = useState([])
useEffect(() => {
setUsers(props.users)
}, [[...props.users]]);
}
If I for any reason try to access state (users) from either my child or parent components I get my initial value, which is an empty array, not my updated value from getUsers(), generally with a Parent Class component I'd have no trouble accessing that info, but it seems like functional components behave diffently? or is it caused by the useEffect? generally I'd use a class component for the parent but some libraries I use rely on Hooks, so I'm kind of forced to use functional components.
There are a couple of mistakes the way you are trying to access data and passing that data.
You should adapt to the concept of lifting up state, which means that if you have users being passed to your Child component, make sure that all the logic regarding adding or removing or updating the users stays inside the Parent function and the Child component is responsible only for displaying the list of users.
Here is a code sandbox inspired by the code you have shared above. I hope this answers your question, do let me know if otherwise.
Also sharing the code below.
import React, { useState } from "react";
export default function Parent() {
const [users, setUsers] = useState([]);
let [userNumber, setUserNumber] = useState(1); // only for distinctive users,
//can be ignored for regular implementation
const setRandomUsers = () => {
let newUser = {};
newUser.name = `user ${userNumber}`;
setUsers([...users, newUser]);
setUserNumber(++userNumber);
};
return (
<div className="App">
<button onClick={setRandomUsers}>Add New User</button>
<Child users={users} />
</div>
);
}
const Child = props => {
return (
props.users &&
props.users.map((user, index) => <div key={index}>{user.name}</div>)
);
};
it doesnt make sense to me that at Child you do const [users, setUsers] = useState([]). why dont you pass down users and setUsers through props? your child's setUser will update only its local users' state value, not parent's. overall, duplicating parent state all around its children is not good, you better consume it and updating it through props.
also, once you do [[...props.users]], you are creating a new array reference every update, so your function at useEffect will run on every update no matter what. useEffect doesnt do deep compare for arrays/objects. you better do [props.users].

Prevent Unnecessary Re-rendering of Child Elements

I am creating a global notifications component in react that provides a createNotification handle to its children using Context. The notifications are rendered along with props.children. Is there anyway to prevent the re-rendering of the props.children if they haven't changed?
I have tried using React.memo and useMemo(props.children, [props.children]) to no prevail.
const App = () => {
return (
<Notifications>
<OtherComponent/>
</Notifications/>
);
}
const Notifications = (props) => {
const [notifications, setNotifications] = useState([]);
const createNotification = (newNotification) => {
setNotifications([...notifications, ...newNotification]);
}
const NotificationElems = notifications.map((notification) => <Notification {...notification}/>);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
const OtherComponent = () => {
console.log('Re-rendered');
return <button onClick={() => useContext(NotificationContext)(notification)}>foo</button>
}
Every time a new notification is created, props.children is re-rendered even though nothing actually changes within it. It just adds elements along side it. This can be quite expensive if you have a big app and everything re-renders for each notification that shows up. If there is no way to prevent this, how can I split it up so I can do this:
<div>
<OtherComponent/>
<Notifications/>
</div>
and share with OtherComponent the createNotification handle?
You need to use the useCallback hook to create your createNotification imperative handler. Otherwise you will create a new function on every render of the Notifications component which will lead to all components consuming your context to re-render because you always pass a new handler whenever you add a notification.
Also you likely didn't mean to spread the newNotification into the array of notifications.
The next thing you need to do is to provide the updater callback version of setState inside setNotifications. It gets passed the current list of notifications that you can use the append the new one. This makes your callback independent of the current value of the notification state. It is usually an error to update the state based on the current state without using an updater function because react batches multiple updates.
const Notifications = props => {
const [notifications, setNotifications] = useState([]);
// use the useCallback hook to create a memorized handler
const createNotification = useCallback(
newNotification =>
setNotifications(
// use the callback version of setState
notifications => [...notifications, newNotification],
),
[],
);
const NotificationElems = notifications.map((notification, index) => <Notification key={index} {...notification} />);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
Another issue is that you conditionally call the useContext hook which is not allowed. Hooks must be called unconditionally:
const OtherComponent = () => {
// unconditiopnally subscribe to context
const createNotification = useContext(NotificationContext);
console.log('Re-rendered');
return <button onClick={() => createNotification({text: 'foo'})}>foo</button>;
};
Fully working example:

Using useMemo instead of React.memo syntax issue

I need to make a demonstration of using React Hooks useMemo. I have working code that is as follows that does what I want:
const SpeakerCardDetail = React.memo(
({id,...
I found a link that shows that I could use syntax more like this but I can't figure it out exactly.
This is as far as I got:
const SpeakerDetail = React.useMemo(() => {
({ id,
Clearly not it though. I do get that React.memo solves the problem but I do need to show useMemo in action and am hoping there is an alternative syntax that I can use.
React.memo and React.useMemo are not equivalent at all (don't rely on naming similarity). Here's a quote from React.memo doc:
React.memo is a higher order component.
So it's a HOC that can optimize rendition of your component given that it renders the same output with the same properties.
React.useMemo on the other hand is more generic and returns a memoized value:
Pass a “create” function and an array of dependencies. useMemo will
only recompute the memoized value when one of the dependencies (either a or b) has
changed.
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);
And while it can be hacked to be used instead of React.memo, it's not its purpose and it will add to the confusion more than it will help. useMemo is a hook and is subject to the certain usage rules.
And there's this warning as well:
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.
While memo is a HOC and useMemo is a hook, you can use them the achieve the same result.
For context, HOC is an older React pattern that has been used for many years with class-based and functional components alike. You can still use it today (there's no plan for deprecation).
Hooks is a relatively new concept (about a year) that enhances functional components and in many cases drastically simplifies code. That's why many developers are moving towards using hooks.
Anyway, both memo and useMemo take two arguments: a function and props. If none of the props change on subsequent re-renders, the function is not executed again and instead returns the previous result. This, in effect, replaces shouldComponentUpdate callbacks, with a purely functional approach.
With memo, your code would look like this:
const SpeakerCardDetail = React.memo(
(props) => <div>{props.name}</div>
)
With useMemo, you'd write:
const SpeakerCardDetail = (props) => useMemo(() => <div>{props.name}</div>)
Notice that useMemo is used inside of your component function, while memo wraps the function.
More traditionally, useMemo could be written as:
function SpeakerCardDetail(props) {
return useMemo(
() => <div>{props.name}</div>
)
}
Now, the code above would re-render every time, making the useMemo function a bit useless. To make it work its magic, we need to add the second argument. (memo still works even without specifying the second argument but you can add it to customize it)
There's a slight difference in the format of the second argument. memo expects a function that compares previous and current props, just like shouldComponentUpdate does for class components.
const SpeakerCardDetail = React.memo(
(props) => <div>{props.name}</div>
,
// return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false
(prevProps, nextProps) => prevProps.name === nextProps.name
)
useMemo, on the other hand, expects an array as the second argument. Whenever the values in the array change, the function would be executed again.
function SpeakerCardDetail(props) {
return useMemo(
() => <div>{props.name}</div>
,
[props.name]
)
}
There's really no more magic than that. Both memo and useMemo are used to memoize the result of a function, the only difference is memo is a HOC (and can be used to wrap both class and functional components) which useMemo is a hook (and can only be used inside functional components).
To summarise React.memo vs useMemo / TLDR:
React.memo is a higher-order component (HOC for short) that will memoize a react component based on the props.
export function SomeComponent({ num }) {
return <p>{num * 10}</p>
}
export default React.memo(SomeComponent, function areEqual(
prevProps,
nextProps
) {
if (prevProps.num !== nextProps.num) {
return false
}
return true
})
useMemo is a react hook that will memoize the value that is returned from the function you provide it.
export function SomeComponent({ num }) {
const res = useMemo(() => num * 10, [num])
return <p>{res}</p>
}
Source
React.Memo
Using React.memo will cause React to skip rendering a component if its props have not changed.
Example:
const Child = React.memo(props => {
console.log("rendered");
return <React.Fragment>{props.name}</React.Fragment>;
});
class App extends React.Component {
state = {
value: 1,
name: "Jioke"
};
handleClick = () => {
this.setState({
value: this.state.value + 1
});
};
render() {
return (
<React.Fragment>
<Child name={this.state.name} />
<div>{this.state.value}</div>
<button onClick={this.handleClick}>+</button>
</React.Fragment>
);
}
}
When we click button, Child Component does not re-render(rendered is displayed only 1)
Notes: The more props, the more calculations, comparison(check is render or not) added comparison cost is not worth it for a "simple" component in terms of render, reconcile, DOM change and side-effect costs. So be careful to decide use it or not
UseMemo
useMemo will cache a value so that it does not need to be recalculated each times components is re-render. It saves return value of function and returns if the inputs are not changed.
Example:
import { useState, useMemo } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const calculation = useMemo(() => expensiveCalculation(count), [count]);
const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
};
return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Expensive Calculation</h2>
{calculation}
</div>
</div>
);
};
const expensiveCalculation = (num) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};
ReactDOM.render(<App />, document.getElementById('root'));
we have an expensive function that runs on every render.
When changing the count or adding a todo, you will notice a delay in execution.
So when we use useMemo the expensive function will only run when its dependencies have changed.
In the following example, the expensive function will only run when count is changed and not when todo's are added.
Please use the below simple example to better understand the answers above. The example demonstrates these scenarios in the simplest way:
memo
If the props do not change, the component won't be re-rendered.
useMemo
If the dependencies do not change, the component won't be re-rendered.
If the dependencies do not change, the function (useful for expensive functions) won't be re-run.
// index.js
import React , { useState, useMemo } from 'react';
import ReactDOM from 'react-dom/client';
const Child1 = (props) => {
console.log("Child1");
return <p>Child 1</p>;
};
const Child2 = React.memo((props) => {
console.log("Child2");
return <p>Child 2</p>;
});
const Child3 = (props) => {
console.log("Child3");
return <p>Child 3</p>;
};
const expensiveCalculation = (label) => {
console.log(label);
return label;
};
function App() {
console.log("App");
const [count, setCount] = useState(0);
const child3 = useMemo(() => <Child3 />, []);
const calculation1 = expensiveCalculation("Expensive calculation without useMemo");
const calculation2 = useMemo(() => expensiveCalculation("Expensive calculation with useMemo"), []);
return (
<>
<button onClick={() => {setCount(c => c + 1);}}>Increase count</button>
<p>Current count: {count}</p>
<Child1 />
<Child2 />
{child3}
<p>{calculation1}</p>
<p>{calculation2}</p>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Input/output:
// Initial render
App
Expensive calculation without useMemo
Expensive calculation with useMemo
Child1
Child2
Child3
// Click on the "Increase count" button and observe the impact of memo and useMemo.
App
Expensive calculation without useMemo
Child1
Notes:
I have taken a very simple example to make it easy to explain. Please research a little bit more on the arguments memo and useMemo takes.

Resources