For ever more optimisation / common logic, I want to make a good useSelectors that uses multiple useCallback.
Here useSeletors is to make selectors (like in redux) on useReducer.
So this is my code :
const useSelectors = (state, selectors) => useMemo(
() => selectors.map(selector =>
(...args) => (selector(state, ...args))
),
[state]
);
What I wanted to do is :
const useSelectors = (state, selectors) => useMemo(
() => selectors.map(selector => useCallback(
(...args) => (selector(state, ...args))),
[state]
),
[state]
);
Which is actually causing an error.
I probably can do this :
const useSelectors = (state, selectors) => useMemo(
() => selectors.map(selector => selector(state)),
[state]
);
But I lose the possibility to use a arguments to my selector function.
Maybe it isn't a problem, because there is something that I don't understand yet, about useCallback :
If I use a useCallback without giving arguments but in giving a dependecy, it will almost be like a variable.
At the first time I call it, is it executed ? Next times (without updating the dependency), I directly get the return of the function without execution ?
So what would happen if I put variable arguments (a counter for example) in the callback ?
Will it probably re-executed the function with this new argument even if the dependency doesn't change ?
So did the useCallback structure become useless with arguments ?
The last point, that I want to ask, is : when a callback is executed?
For a useMemo, the function is executed the first time at is declaration ? (Or the first time we use its variable ? => The difference is not really important for this case.)
Did useCallback is called a first time only when it is called with () in the code or at its declaration ?
Example :
const myVar = useMemo(() => 5+5, [dependency]); // executed
const myFunc = useCallback(() => 5+5, [dependency]);
myVar; // 10
myFunc(); // executed => 10
myVar; // 10
myFunc(); // 10
So if it works like this it's better to call useCallBack in the useMemo, to execute the selector only when it is call and not at the mount in my third solution.
This is the main reason why I want to use multiple useCallbacks in my hook, without knowing the number.
[EDIT]
Second proposition with useCallback has no sens because :
const test = useCallback(() => {
console.log('test is executed');
return 'test';
}, [state]);
console.log(test());
console.log(test());
Logs :
test is executed
test
test is executed
test
And not that I expected :
test is executed
test
test
So it couldn't do better than the first
useCallback returns a function. When one of the dependency of that hook (for example, some counter) is changed useCallback invoked again and return new function. It does because the function passed to useCallback doesn't know that the counter is changed until useCallback re-executes it again.
useMemo works the same but it returns a value. In theory you can implement useCallback using useMemo, returning function instead of value. I mean useCallback(fn, deps) is the same as useMemo(() => fn, deps).
You can read more about it in the React docs.
In your code you should use useMemo and don't use useCallback inside useMemo because you'll get an error and it doesn't unnecessary because it uses when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders. It isn't your case. Your first example of code is correct.
Related
I have a simple custom hook that returns some functions:
const useCustom = () => {
const someReduxValue = useSelector(...);
const fn1 = useCallback(() => {
// do something with someReduxValue
}, [...]);
const fn2 = useCallback(() => {
// may include some complex operations like reducing/mapping etc
}, [...]);
return useMemo(() => ({
fn1,
fn2,
}), [...]);
};
My question: is there any advantage in wrapping each functions (fn1, fn2) into useCallbacks and then wrapping all what hook returns into useMemo?
Note: no state used in this hook.
It totally depends on your function.
useMemo and useCallback must be used carefully Because it fills the memory!
useMemo Returns a memoized value.
For example:
you have a function for checking that number is prime or not?
this function examin number 9851812358!
this is a big number for checking that and suppose in every rerender this function check this huge number for prime number!!!
useMemo memoized this value (is prime or not) and in re-rendering just returns last result and doesn't calculate this number again and again.
useMemo memoized value but useCallback returns a memoized callback!
Reactjs Docs:
will return a memoized version of the callback that only changes if
one of the dependencies has changed. This is useful when passing
callbacks to optimized child components that rely on reference
equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
now. you should decide your function needs useMemo and useCallback or not!
I use this pattern sometimes where I declare a curried function inside useCallback.
const Child = ({ handleClick }) => {
return (
<>
<button onClick={handleClick("foo")}>foo</button>
<button onClick={handleClick("lorem")}>lorem</button>
</>
);
};
export default function App() {
const [state, setState] = useState("");
const handleClick = useCallback(
(newState) => () => {
setState(newState);
},
[]
);
return (
<div className="App">
<Child handleClick={handleClick} />
<p>{state}</p>
</div>
);
}
because I want to pass arguments from JSX to the event handlers and to avoid multiple handlers.
When the component rerenders, handleClick will be called and the function that is returned will be assigned to the onClick prop, but will it be a new function every time or will the nested function also get memoized by useCallback?
PS: This is a simple example. Assume a useCallback usage with multiple dependencies
Edit
Following answer refers to the initial version of the OP's code in which handleClick was not passed as a prop to Child component.
You don't really need useCallback in this case.
Actually, it would be better not to use the useCallback hook in this case because:
Callback function passed to useCallback is created everytime your component re-renders. As a result, a new function is getting created anyways, just like it would without the use of useCallback
Getting a memoized version of the handleClick function doesn't provides any benefit in your case. Memoization would be useful if handleClick is passed as a prop to child component(s).
...but will it be a new function every time or will the nested
function also get memoized by useCallback?
No, the nested function won't be memoized.
handleClick is a memoized function but that memoized function returns a new function everytime it executes.
Invoking handleClick is just like invoking any other function - anything declared inside its body is created everytime it is invoked.
It should, either curry or not, it's a function instance, as long as you want to keep an old function instance, you can use it.
But don't use it because the prop onClick, because when your component renders, this button has to render regardless of the onClick. So your line can be simply as
const onClick = value => () => { setState(value) }
Or you can even promote this to a global function.
const handle = setState => value => () => { setState(value) }
NOTE: i don't want to say useCallback most of time is useless. But actually if you don't know why you want to use a useMemo, don't use a useCallback. They are designed to skip an assignment, but not designed to skip a render, or in your case, to improve reusability. (not for that purpose).
I have a function named getAllEmployees which I export from another file.
const getAllEmployees = () => {
return [1,2,3,4,5,6,7,8,9]
}
export { getAllEmployees }
now I use React.useState(getAllEmployees). this gives me the result, when I call like React.useState(getAllEmployees()) it also gives me same result, event when call like React.useState(() => getAllEmployees()) this also gives me the same result.
import here
import { getAllEmployees } from './Service/Service'
use with useState
const [result] = useState(getAllEmployees ) or
const [result] = useState(getAllEmployees()) or
const [result] = useState(() => getAllEmployees())
console.log(result)
for all of those result is
(9) [1, 2, 3, 4, 5, 6, 7, 8, 9]
My question is why they gives me same results, and which is the right way?
Why they gives me same results?
const [result] = useState(getAllEmployees)
React useState hook can take a lazy initializer function, to be invoked when the component is mounting and state is being initialized. You are passing a callback for React to call and initialize state with the return value.
const [result] = useState(getAllEmployees())
This is simply immediately invoking the function and passing the return value to the hook to be the initial state. Note that this function will be called each render cycle and the result only used from the initial render, all subsequent calls will be ignored. If the function is "heavy" it can adversely affect performance since it is needlessly called.
const [result] = useState(() => getAllEmployees())
This is the same as 1, but you've passed an anonymous initializing function.
Which is the right way?
The right way is the one that works for your use case, is easy to read and understand, and maintain. 1 and 3 are correct and functionally equivalent, though 3 is a bit redundant/unnecessary. 2 should be avoided. As noted above this function will be called each time the component renders and since the initial state value passed to useState is ignored on all subsequent renders 2 is just doing a lot of unnecessary work for no reason.
In the example below, I'm getting in a loop when I call the onError prop in fetchItems(). I don't understand why, when called, it triggers hooks depending on it. How can I fix this? Thanks!
const Component = ({onError}) => {
const [items, setItems] = useState([]);
const itemsRef = useRef(items);
const fetchItems = useCallback(() => {
const [first] = itemsRef.current;
fetchNewItemsSince(first || 0).then((newItems) => {
setItems((oldItems) => [...oldItems, ...newItems]);
}).catch(onError);
}, [onError]);
// Update ref to dispose closure on `items` state
useEffect(() => {
itemsRef.current = items;
}, [items]);
// Call once on mount
useEffect(() => {
fetchItems();
}, [fetchItems]);
// Make an interval
useEffect(() => {
const id = setInterval(fetchItems, ONE_MINUTE);
return () => {
clearInterval(id);
};
}, [fetchItems]);
};
Try setting the initial state with a function
Const [foo, setFoo] = useState(() => ‘foo’)
Your useCallback for that state instance probably runs once, correct me if I’m wrong, so if you set a function for useState it will only run once, consider is a component did mount but no update.
Maybe this is practice but I have never called something like [onError] without setting an error state, because that’s what I think recognizes it.
So React is this great thing that renders certain components etc. UseEffect is great, I personally don’t use it to change the state, that for me is usually done in the JSX.
I would do like a onClick handler with the UseState method and watch for changes there. Instead your running a function that runs a setState event and watches for an event.
Let me know if that works or not if you need any explanation.
As I said in the comments, onError is triggering a re-render on the parent and therefore the children will also render again.
Someone suggested removing onError from the useCallback array of dependencies. Although it might work, it is considered a bad practice because can lead to memory-leak. Have you tried to remove the useCallback wrap around your function?
I am finding myself in a weird situation. I am implementing an hook and I cannot manage to achieve what I want.
I have something like this:
const appHook = props => {
const [foo, setFoo] = React.useState([]);
const [bar, setBar] = React.useState([]);
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
setBar(getBar(foo.listToFilter));
}, [props.fooId]);
const getCurrentBlockTrade = (arrayToFilter, number) =>
arrayToFilter.filter(array => array.id === number);
const getSubOutList = (...) => {
...
};
return (<div>something</div>)
}
My issue is that the function setFoo is properly executed, so foo state is a new array, but setBar that depends on the state of foo, receives an empty array. Basically setBar is executed before setFoo finished so the getBar function receives an empty array.
What is the right way to manage this kind of dependency?
Thanks,
F.
TL;DR; Your solution is likely kind user's answer
Below I'll describe what I've thought and learned so far throughout researches, and come up with 5 suggestions/solutions from people, via blogs,...
You've said:
My issue is that the function setFoo is properly executed, so foo state is a new array, but setBar that depends on the state of foo, receives an empty array. Basically setBar is executed before setFoo finished so the getBar function receives an empty array.
You're true. Basically because in React (both Hooks and class component), setState is asynchronous. What does it mean? It means that setSomething just tells React to re-render the component later. It doesn't magically replace the const something variable in the current running function — that's not possible.
const [foo, setFoo] = useState(0)
function handleClick() {
setFoo(42)
// we declared foo with const, you "obviously" shouldn't expect this
// to "somehow" immediately change `foo` to 42
console.log(foo);
// it's 0 in this render, BUT on next render, `foo` will be 42
}
Solution 1.
The easiest technique to you is to store the newly calculated value of foo in a variable, then use that newly calculated value to both setFoo and setBar - a quite popular technique tho.
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);
// or: shouldn't use this, only to demonstrate the callback syntax in
// the new setState Hook (different than the old callback syntax setState):
React.useEffect(() => {
setFoo(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setBar(getBar(newFoo.listToFilter));
return newFoo;
})
}, [props.fooId]);
Solution 2.
Another technique can be found here: https://stackoverflow.com/a/54120692/9787887 is using useEffect to setBar with the dependency list whose foo.
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);
Despite the answer get 27 upvotes, I think it's just overcomplicated the situation, and also (as I know) make the component unnecessarily rerender 2 times instead of 1, should be avoided.
Solution 3.
Another solution that might work is to use async/await to make the state changes triggered asynchronously, to lead the changes not be batched (regarding this answer https://stackoverflow.com/a/53048903/9787887)
React.useEffect(async () => {
await setFoo(getFoo(props.fooList, props.fooId));
await setBar(getBar(foo.listToFilter));
}, [props.fooId]);
// no, actually this will not work!! it'll throw you an (annoyed) error
// the actual working code is:
React.useEffect(() =>
const setFooAndBar = async () => {
await setFoo(getFoo(props.fooList, props.fooId));
await setBar(getBar(foo.listToFilter));
}
setFooAndBar();
}, [props.fooId]);
You see, the working code is again another overcomplicated (and bad) solution, (but should be introduced anyway??).
Solution 4.
Another solution that gaearon mentioned is to use useReducer
With Hooks you could also useReducer to centralize state update logic and avoid this pitfall.
Another his insight:
the recommended solution is to either use one variable instead of two (since one can be calculated from the other one, it seems), or to calculate the next value first and update them both using it together. Or, if you're ready to make the jump, useReducer helps avoid these pitfalls.
But it again seems to be another overcomplex suggestion to this case, doesn't it?
Solution 5.
The last suggestion is a comment of gaearon, tell you to rethink about your state dependence, is the state dependence really needed?
the best solution is simply to not have state that is calculated from another state. If this.state.y is always calculated from this.state.x, remove this.state.y completely, and only track this.state.x. And calculate what you need when rendering instead
Thank you for being patient enough to read to here :)).
Setting a state is an asynchronus process. So setBar(getBar(foo.listToFilter)); calling this foo is the empty array. You can use another useEffect for this
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);
setState is an asynchronous function, that's why you are receiving an empty array in setBar function. Basically you can't be sure that the state will be updated before the second setState evaluates.
Why not to simply refer to the props in both cases?
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);