I have a pretty decent understanding of how useCallback works. Figuring out when to use it though seems to be subjective amongst me and my colleagues though. I'm curious what everyone else thinks about our current dilemma.
Imagine we have a component that is dispatching an action to redux as a result of something being selected:
const SelectionComponent = props => {
const dispatch = useDispatch()
const handleSelect = (selection) => {
dispatch(actions.updateSelection(selection))
}
return <Select onSelect={handleSelect} ... />
}
My colleague believes we should wrap handleSelect in a useCallback to make sure the function has a stable identity since it's being passed as a callback to a child component:
const SelectionComponent = props => {
const dispatch = useDispatch()
const handleSelect = useCallback((selection) => {
dispatch(actions.updateSelection(selection))
}, [dispatch])
return <Select onSelect={handleSelect} ... />
}
So my question is, which is the better solution, and why?
Some notes:
useDispatch returns a dispatch function with a stable identity
No rerenders or performance issues occur without the useCallback
EDIT
Just to clarify, this is a question on whether or not we should memoize a function on the basis of maintaining a stable identity when passed to a child component, even if the component tree is not expected to rerender for any reason.
Memoizing handleSelect only makes a difference if Select is memoized as well. Remember - when a component re-renders it also re-renders all of it's children by default (regardless of if their props change or not).
Therefore without knowing how Select is implemented, we can't really say if the useCallback actually has any affect much less if it is "better".
Usually, this type of optimization is unnecessary though. Unless your Select is complex or expensive in some way, you probably do not need to memoize either.
same my question,
I just find the answer https://github.com/reduxjs/react-redux/issues/1468
"dispatch will be the same function reference the entire time. (In fact, in earlier versions of React-Redux, passing a new store reference was forbidden. We do now support changing it at runtime, but realistically you probably won't do that.)
However, the ESLint rule doesn't know that - it just knows that dispatch isn't a built-in React hook return value, so it might change, and therefore it tells you it should be added to the dependencies array just in case it ever does change."
Related
In terms of writing components, which would be the preferred way to write below component? Assume that removeCard is outside of shown scope, ie. redux action.
My assumption would be that ComponentCardB would be, as it avoids passing an unnecessary argument which would be in the scope anyway. I imagine in terms of performance in the grand scheme of things, the difference is negligible, just more of a query in regards to best practise.
TIA
const ComponentCardA = (id) => {
const handleRemove = (cardId) => {
removeCard(cardId);
};
<div onClick={() => handleRemove(id)} />;
};
const ComponentCardB = (id) => {
const handleRemove = () => {
removeCard(id);
};
<div onClick={handleRemove} />;
};
With functional components like that, yes, there's no reason for the extra layer of indirection in ComponentCardA vs ComponentCardB.
Slightly tangential, but related: Depending on what you're passing handleRemove to and whether your component has other props or state, you may want to memoize handleRemove via useCallback or useMemo. The reason is that if other props or state change, your component function will get called again and (with your existing code) will create a new handleRemove function and pass that to the child. That means that the child has to be updated or re-rendered. If the change was unrelated to id, that update/rerender is unnecessary.
But if the component just has id and no other props, there's no point, and if it's just passing it to an HTML element (as opposed to React component), there's also probably no point as updating that element's click handler is a very efficient operation.
The second option is better way because using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison.
Also if you don't want to use syntax with props.id you rather create function component with object as parameter:
const Component = ({id}) => { /* ... */ }
Of course using arrow function is also allowed but remember, when you don't have to use them then don't.
There are articles related to what is useCallback and to use it when necessary. But in a big project it's very difficult to understand when to start using this.
So, if I use it for every use case, will it hit the performance of the application.
useCallback and useMemo are helper hooks with the main purpose off adding an extra layer of dependency check to ensure synchronicity. Usually you want to work with useCallback to ensure a stable signature to a prop which you know how will change and React doesn't.
A function(reference type) passed via props for example
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [])
}
Lets assume you have a child component which uppon mounting must set some state in the parent (not usual), the above code will generate a warning of undeclared dependency in useEffect, so let's declare setParentState as a dependency to be checked by React
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [setParentState])
}
Now this effect runs on each render, not only on mounting, but on each update. This happens because setParentState is a function which is recreated every time the function Component gets called. You know that setParentState won't change it's signature overtime so it's safe to tell React that. By wrapping the original helper inside an useCallback you're doing exactly that (by adding another dependency check layer).
const Component = ({ setParentState }) =>{
const stableSetter = useCallback(() => setParentState(), [])
useEffect(() => setParentState('mounted'), [stableSetter])
}
There you go. Now React knows that stableSetter won't change it's signature inside the lifecycle therefore the effect do not need run unecessarily.
On a side note useCallback it's also used like useMemo, to optmize expensive function calls (memoization).
The two main purposes of useCallback are
Optimize child components that rely on reference equality to prevent unnecessary
renders. Font
Memoize expensive computing calculations
Is there any drawback/performance?
useCallback is mainly used to optmize performance by changing stuff only when it's needed. It does that by adding a layer of dependencies just like useEffect (and similar to how React itself knows how something must change in the DOM), but as every single performance optimization technique, it can shoot backwards, if you fill your entire application with unecessary useCallback, useMemo, React.memo the performance will go the other way around.
So the key is to use wisely, carefully choosing what needs to have a stable signature and what doesn't.
Is useState's setter able to change during a component life ?
For instance, let's say we've got a useCallback which will update the state.
If the setter is able to change, it must be set as a dependency for the callback since the callback use it.
const [state, setState] = useState(false);
const callback = useCallback(
() => setState(true),
[setState] // <--
);
The setter function won't change during component life.
From Hooks FAQ:
(The identity of the setCount function is guaranteed to be stable so it’s safe to omit.)
The setter function (setState) returned from useState changes on component re-mount, but either way, the callback will get a new instance.
It's a good practice to add state setter in the dependency array ([setState]) when using custom-hooks. For example, useDispatch of react-redux gets new instance on every render, you may get undesired behavior without:
// Custom hook
import { useDispatch } from "react-redux";
export const CounterComponent = ({ value }) => {
// Always new instance
const dispatch = useDispatch();
// Should be in a callback
const incrementCounter = useCallback(
() => dispatch({ type: "increment-counter" }),
[dispatch]
);
return (
<div>
<span>{value}</span>
// May render unnecessarily due to the changed reference
<MyIncrementButton onIncrement={dispatch} />
// In callback, all fine
<MyIncrementButton onIncrement={incrementCounter} />
</div>
);
};
The short answer is, no, the setter of useState() is not able change, and the React docs explicitly guarantee this and even provide examples proving that the setter can be omitted.
I would suggest that you do not add anything to the dependencies list of your useCallback() unless you know its value can change. Just like you wouldn't add any functions imported from modules or module-level functions, constant expressions defined outside the component, etc. adding those things is just superfluous and makes it harder to read your handlers.
All that being said, this is all very specific to the function that is returned by useState() and there is no reason to extend that line of reasoning to every possible custom hook that may return a function. The reason is that the React docs explicitly guarantee the stable behavior of useState() and its setters, but it does not say that the same must be true for any custom hook.
React hooks are still kind of a new and experimental concept and we need to make sure we encourage each other to make them as readable as possible, and more importantly, to understand what they actually do and why. If we don't it will be seen as evidence that hooks are a "bad idea," which will prohibit adoption and wider understanding of them. That would be bad; in my experience they tend to produce much cleaner alternatives to the class-based components that React is usually associated with, not to mention the fact that they can allow organizational techniques that simply aren't possible with classes.
When creating a React app, if I use the hook useSelector, I need to adhere to the hooks invoking rules (Only call it from the top level of a functional component). If I use the mapStateToProps, I get the state in the props and I can use it anywhere without any issues... Same issue for useDispatch
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
Redux store state can be read and changed from anywhere in the component, including callbacks. Whenever the store state is changed the component rerenders. When the component rerenders, useSelector runs again, and gives you the updated data, later to be used wherever you want. Here is an example of that and a usage of useDispatch inside a callback (after an assignment in the root level):
function Modal({ children }) {
const isOpen = useSelector(state => state.isOpen);
const dispatch = useDispatch();
function handleModalToggeled() {
// using updated data from store state in a callback
if(isOpen) {
// writing to state, leading to a rerender
dispatch({type: "CLOSE_MODAL"});
return;
}
// writing to state, leading to a rerender
dispatch({type: "OPEN_MODAL"});
}
// using updated data from store state in render
return (isOpen ? (
<div>
{children}
<button onClick={handleModalToggeled}>close modal</button>
</div>
) : (
<button onClick={handleModalToggeled}>open modal</button>
);
);
}
There is nothing you can do with mapStateToProps/mapDispatchToProps that you can't do with the useSelector and useDispatch hooks as well.
With that said, there are a couple of differences between the two methods that are worth considering:
Decoupling: with mapStateToProps, container logic (the way store data is injected into the component) is separate from the view logic (component rendering).
useSelector represents a new and different way of thinking about connected components, arguing that the decoupling is more important between components and that components are self contained. Which is better? Verdict: no clear winner. source
DX (Developer experience): using the connect function usually means there should be another additional container component for each connected component, where using the useSelector and useDispatch hooks is quite straightforward. Verdict: hooks have better DX.
"Stale props" and "Zombie child": there are some weird edge cases with useSelector, if it depends on props, where useSelector can run before the newest updated props come in. These are mostly rare and avoidable edge cases, but they had been already worked out in the older connect version. verdict: connect is slightly more stable than hooks. source
Performance optimizations: both support performance optimizations in different ways: connect has some advanced techniques, using merge props and other options hidden in the connect function. useSelector accepts a second argument - an equality function to determine if the state has changed. verdict: both are great for performance in advanced situations.
Types: using typescript with connect is a nightmare. I remember myself feverishly writing three props interfaces for each connected component (OwnProps, StateProps, DispatchProps). Redux hooks support types in a rather straightforward way. verdict: types are significantly easier to work with using hooks.
The future of React: Hooks are the future of react. This may seam like an odd argument, but change to the ecosystem is right around the corner with "Concurrent mode" and "Server components". While class components will still be supported in future React versions, new features may rely solely on hooks. This change will of course also affect third party libraries in the eco system, such as React-Redux. verdict: hooks are more future proof.
TL;DR - Final verdict: each method has its merits. connect is more mature, has less potential for weird bugs and edge cases, and has better separation of concerns. Hooks are easier to read and write, as they are collocated near the place where they are used (all in one self contained component). Also, they are easier to use with TypeScript. Finally, they will easily be upgradable for future react versions.
I think you misunderstand what "top level" is. It merely means that, inside a functional component, useSelector() cannot be placed inside loops, conditions and nested functions. It doesn't have anything to do with root component or components structure
// bad
const MyComponent = () => {
if (condition) {
// can't do this
const data = useSelector(mySelector);
console.log(data);
}
return null;
}
---
// good
const MyComponent = () => {
const data = useSelector(mySelector);
if (condition) {
console.log(data); // using data in condition
}
return null;
}
If anything, mapStateToPtops is located at even higher level than a hook call
the rules of hooks make it very hard to use that specific hook. You still need to somehow access a changing value from the state inside callbacks
To be fair you almost never have to access changing value inside a callback. I can't remember last time I needed that. Usually if your callback needs the latest state, you are better off just dispatching an action and then handler for that action (redux-thunk, redux-saga, redux-observable etc) will itself access the latest state
This is just specifics of hooks in general (not just useSelector) and there are tons of ways to go around it if you really want to, for example
const MyComponent = () => {
const data = useSelector(mySelector);
const latestData = useRef()
latestData.current = data
return (
<button
onClick={() => {
setTimeout(() => {
console.log(latestData.current) // always refers to latest data
}, 5000)
}}
/>
)
}
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
You save time by not writing connect function any time you need to access store, and removing it when you no longer need to access store. No endless wrappers in react devtools
You have clear distinction and no conflicts between props coming from connect, props coming from parent and props injected by wrappers from 3rd party libraries
Sometimes you (or fellow developers you work with) would choose unclear names for props in mapStateToProps and you will have to scroll all the way to mapStateToProps in the file to find out which selector is used for this specific prop. This is not the case with hooks where selectors and variables with data they return are coupled on the same line
By using hooks you get general advantages of hooks, the biggest of which is being able couple together and reuse related stateful logic in multiple components
With mapStateToProps you usually have to deal with mapDispatchToProps which is even more cumbersome and easier to get lost in, especially reading someone else's code (object form? function form? bindActionCreators?). Prop coming from mapDispatchToProps can have same name as it's action creator but different signature because it was overridden in mapDispatchToprops. If you use one action creator in a number of components and then rename that action creator, these components will keep using old name coming from props. Object form easily breaks if you have a dependency cycle and also you have to deal with shadowing variable names
.
import { getUsers } from 'actions/user'
class MyComponent extends Component {
render() {
// shadowed variable getUsers, now you either rename it
// or call it like this.props.getUsers
// or change import to asterisk, and neither option is good
const { getUsers } = this.props
// ...
}
}
const mapDispatchToProps = {
getUsers,
}
export default connect(null, mapDispatchToProps)(MyComponent)
See EDIT 2 at the end for the final answer
Since no one knows how to answer, it seems like the best answer is that you should NOT be using useselector when you need information in other places other than the root level of your component. Since you don't know if the component will change in the future, just don't use useselector at all.
If someone has a better answer than this, I'll change the accepted answer.
Edit: Some answers were added, but they just emphasize why you shouldn't be using useselector at all, until the day when the rules of hooks will change, and you'll be able to use it in a callback as well. That being said, if you don't want to use it in a callback, it could be a good solution for you.
EDIT 2: An answer with examples of all that I wanted was added and showed how useSelector and useDispatch are easier to use.
The redux state returned from the useSelector hook can be passed around anywhere else just like its done for mapStateToProps. Example: It can be passed to another function too. Only constraint being that the hook rules has to be followed during its declaration:
It has to be declared only within a functional component.
During declaration, it can not be inside any conditional block . Sample code below
function test(displayText) {
return (<div>{displayText}</div>);
}
export function App(props) {
const displayReady = useSelector(state => {
return state.readyFlag;
});
const displayText = useSelector(state => {
return state.displayText;
});
if(displayReady) {
return
(<div>
Outer
{test(displayText)}
</div>);
}
else {
return null;
}
}
EDIT: Since OP has asked a specific question - which is about using it within a callback, I would like to add a specific code.In summary, I do not see anything that stops us from using useSelector hook output in a callback. Please see the sample code below, its a snippet from my own code that demonstrates this particular use case.
export default function CustomPaginationActionsTable(props) {
//Read state with useSelector.
const searchCriteria = useSelector(state => {
return state && state.selectedFacets;
});
//use the read state in a callback invoked from useEffect hook.
useEffect( ()=>{
const postParams = constructParticipantListQueryParams(searchCriteria);
const options = {
headers: {
'Content-Type': 'application/json'
},
validateStatus: () => true
};
var request = axios.post(PORTAL_SEARCH_LIST_ALL_PARTICIPANTS_URI, postParams, options)
.then(function(response)
{
if(response.status === HTTP_STATUS_CODE_SUCCESS) {
console.log('Accessing useSelector hook output in axios callback. Printing it '+JSON.stringify(searchCriteria));
}
})
.catch(function(error) {
});
}, []);
}
For callback functions you can use the value returned from useSelector the same way you would use the value from useState.
const ExampleComponent = () => {
// use hook to get data from redux state.
const stateData = useSelector(state => state.data);
// use hook to get dispatch for redux store.
// this allows actions to be dispatched.
const dispatch = useDispatch();
// Create a non-memoized callback function using stateData.
// This function is recreated every rerender, a change in
// state.data in the redux store will cause a rerender.
const callbackWithoutMemo = (event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
};
// Create a memoized callback function using stateData.
// This function is recreated whenever a value in the
// dependency array changes (reference comparison).
const callbackWithMemo = useCallback((event) => {
// use state values.
if (stateData.condition) {
doSomething();
}
else {
doSomethingElse();
}
// dispatch some action to the store
// can pass data if needed.
dispatch(someActionCreator());
}, [stateData, doSomething, doSomethingElse]);
// Use the callbacks.
return (
<>
<div onClick={callbackWithoutMemo}>
Click me
</div>
<div onClick={callbackWithMemo}>
Click me
</div>
</>
)
};
Rules of hooks says you must use it at the root of your component, meaning you CANT use it anywhere.
As Max stated in his answer just means that the hook statement itself must not be dynamic / conditional. This is because the order of the base hooks (react's internal hooks: useState, etc) is used by the backing framework to populate the stored data each render.
The values from hooks can be used where ever you like.
While I doubt this will be close to answering your complete question, callbacks keep coming up and no examples had been posted.
not the answer but this hook can be very helpful if you want to get decoupled nature of mapDispatchToProps while keeping simplicity and dev experience of hooks:
https://gist.github.com/ErAz7/1bffea05743440d6d7559afc9ed12ddc
the reason I don't mention one for mapStatesToProps is that useSelector itself is more store-logic-decoupling than mapStatesToProps so don't see any advantage for mapStatesToProps. Of course I dont mean using useSelector directly but instead create a wrapper on it in your store files (e.g. in reducer file) and import from there, like this:
// e.g. userReducer.js
export const useUserProfile = () => useSelector(state => state.user.profile)
With react hooks now available should I in case of functional components wrap every function passed with props with useCallback and every other props value with useMemo?
Also having custom function inside my component dependent on any props value should I wrap it with useCallback?
What are good practices to decide which props or const values from component wrap with this hooks ?
If this improves performance why not to do it at all times ?
Lets consider custom button where we wrap click handler and add custom logic
function ExampleCustomButton({ onClick }) {
const handleClick = useCallback(
(event) => {
if (typeof onClick === 'function') {
onClick(event);
}
// do custom stuff
},
[onClick]
);
return <Button onClick={handleClick} />;
}
Lets consider custom button where we wrap click handler and add custom logic on condition
function ExampleCustomButton({ someBool }) {
const handleClick = useCallback(
(event) => {
if (someBool) {
// do custom stuff
}
},
[someBool]
);
return <Button onClick={handleClick} />;
}
Should i in this two cases wrap my handler with useCallback ?
Similar case with use memo.
function ExampleCustomButton({ someBool }) {
const memoizedSomeBool = useMemo(() => someBool, [someBool])
const handleClick = useCallback(
(event) => {
if (memoizedSomeBool) {
// do custom stuff
}
},
[memoizedSomeBool]
);
return <Button onClick={handleClick} />;
}
In this example I even pass memoized value to useCallback.
Another case what if in the component tree many components memoize same value ? How does this impact performance ?
Not worth it, for multiple reasons:
Even official docs say you should do it only when necessary.
Keep in mind that premature optimization is the root of all evil :)
It makes DX (developer experience) far worse: it's harder to read; harder to write; harder to refactor.
When dealing with primitives (like in your example) memoizing costs more CPU power than not doing it. A primitive value doesn't have a concept of references, so there's nothing to memoize in them. On the other hand, memoization itself (as any other hook) does require some tiny processing, nothing is for free. Even though it's tiny, it's still more than nothing (compared to just passing a primitive through), so you'd shoot your own foot with this approach.
To put all together - you'd waste more time typing all the hooks than a user would gain on having them in the application if you want to put them everywhere. The good old rule applies: Measure, then optimize.
I agree with the principles proposed by #jalooc
To give some more insight about the exhibited use cases in the OP, here is my advice:
Expensive children renders
function Component() {
const callback = useCallback(() => { dostuff }, [deps])
return <Child prop={callback} />
}
The above makes sense if Child is a very expensive component to render. As such, it is probably exported like so:
function Child() {
...this takes significant CPU...
}
// Export as a pure component
export default React.memo(Child)
Expensive computations
function Component({ foo }) {
// This very expensive computation will only run when it's input (foo)
// changes, allowing Component to re-render without performance issues
const bar = useMemo(() => {
... something very complicated with `foo` ...
}, [foo])
return <div>{bar}</div>
}
Conclusion
Do what makes sense or that have measured bad performance
A function declaration inside a component changes at each render. If this causes derived expensive computations, memoize it (useCallback) or move it outside the scope.
If a component itself is expensive to render, make it pure with React.memo, with the help of #2 if necessary
If something IS itself expensive to re-compute, memoize it (useMemo)
Do what makes sense or that have measured bad performance
I'd argue that there's nothing wrong with putting useCallback everywhere you create a function. The same points are applicable to useMemo; but I'll be mentioning only useCallback since, for the sake of brevity.
As a concession, I must mention that this isn't a cornerstone in your development process, and you must proceed with a solution that your team is comfortable with, whether it's using useCallback or not.
My main argument for using such memoization hooks "extensively" is that you don't have to think of potential performance issues on the front of "reference update-induced" rerendering if you do it. Fewer things to think about = good. More time and energy to solve the actual problems.
Many say "premature optimization is bad". Well, cargo cult isn't good either, and this citation is a pure example of such, taken too far out of context, and with a premise of having an authority behind it. here used to be a good summary on that, unfortunately, the account is taken down currently, but it's available on archive.org.
The concerns of "premature optimization" in this case are structural changes to the code and impaired readability/write-ability.
Structurally, these changes are for the better, making you separate your components.
Readability-wise, there's an extra wrapper, but that comes together with strict tracking of your function dependencies, which otherwise is done implicitly. Dependencies become explicit as they should be since in this system they play a too important role to swipe it under the rug. Readability, therefore, only wins. Although it's harder to write now, the "ritual" is always the same. Readability is more important than the convenience of writing a function.
(Just make sure you use eslint to track your dependencies; it could be a headache when you forget to add a dependency and it gets cached.)
So, "write now, optimize later" - thanks, I'd pass. To me, it's a negligibly cheap optimization that is justified enough and is reasonable enough, if your team is ready to accept the arguments above. It's also fine if they aren't: the topic itself isn't something to die for. It's just a small quality-of-life tool.