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.
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 am using a hook component and several state variables. I have read about using useEffect() with params to get a kind of callback after updating a state. Example:
export const hookComponent = () => {
const [var, setVar] = useState(null);
useEffect(() => {
//do things
}, [var])
}
In this example, useEffect() would be executed on every setVar() call. In my case, I do not want to execute useEffect() everytime, but only on specific occasions.
I would like to give the setVar() some kind of information which I can use in useEffect() like setVar(newValue, true).
Note: I do not want to store this information in var.
Is there a way to do this?
Like Nizar said, simple conditional check on 'var' in useEffect
If expensive calc you can
const expensiveValue = useMemo(() => {
// other logic here if needed
// could even be simple return var=='x'?true:false, although this would be easier to do in the useEffect hook?
return computeExpensiveValue(var);
},[var]);
useEffect(() => {
//do things
//expensiveValue only changes when you want it to from the memo
}, [expensiveValue])
Thank you sambomartin and Nizar for your input.
For everyone looking for an answer:
After some further research I found 3 possible solutions:
Use a class component. If you really are dependent on that state update to be completed switch to a class component, which allows you to give the setState() a callback as a second param.
Use the useRef hook to determine where your state update is comming from. You can use this information in the useEffect() method.
Get independent from the state. I used this solution and externalized my callback function with the drawback of giving it every parameter on every call, although they are present in the component the states are saved.
As far as I know, the useEffect only triggers if the dependency value changes, not simply by executing setValue.
I offer you three solutions, the first one, close to what you want but without using useEffect hook, the second one is an extension of the first one, that may be required if you need control over the previous state, and the third, more general, like comments say, though it won't be triggered if the state is the same, even if you execute setValue.
First solution: Wrap your set value with another function that definitely controls what may happen after or before the new state:
export default function MyComponent() {
const [state, setState] = useState(null);
const handleChangeSetState = (nextState, flag) => {
if (flag) {
specialUseCaseCb();
}
setState(nextState);
};
return <div>{/* ... */}</div>;
}
Second solution: Wrap your set value with another function, like in the solution 1, and ask for the previous or next state within setState inner callback:
export default function MyComponent2() {
const [state, setState] = useState(0);
const handleChangeSetState = (increment, flag) =>
setState((prevState) => {
const nextState = prevState + increment;
// you may need prevState or nextState for checking your use case
if (flag) {
specialUseCaseCb();
}
return nextState;
});
return <div>{/* ... */}</div>;
}
Third solution: use useEffect hook to follow changes, remember though that setState won't re-trigger useEffect hook if the state is the same:
export default function MyComponent3() {
const [state, setState] = useState("");
// notice that this will only be triggered if state changes
useEffect(() => {
if (state !== "my-special-use-case") return;
specialUseCaseCb();
}, [state]);
return <div>{/* ... */}</div>;
}
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.
So as you probably know, in normal mode, we use update dependencies to get notice when the state updated, like this:
const [val, setVal] = useState();
useEffect(() => {}, [val]]);
But in my case, I have an array in my state and I'm trying to update it in a loop in my useEffect like this:
const [val, setVal ] = useState([...]);
useEffect(() => {
anotherArr.forEach(i => {
// get val and modify some indexes
setVal(modifiedValuesArray);
}
}, []);
In this case, every time forEach loop runs, I'm getting the initial val (I know because val is not a
dependency of useEffect) but if I put it as a dependency, it will update twice. what is the solution for this?
EDIT: Basically, I mean when I update state in a round of loop in useEffect, on the next round, I'm not getting the updated state but the initial state before entering the loop. And I know, that is because of the nature of useEffect which gives us a memorized value of state (since we didn't pass it as a dependency to avoid the additional execution), but what is the solution in these types of scenarios.
I came across this answer:(https://stackoverflow.com/a/59422750/2728431) and it solved my problem.
for getting updated state, (as #usafder said in a comment), we need to pass state as a value in an arrow function just like this:
const [val, setVal ] = useState([...]);
useEffect(() => {
anotherArr.forEach(i => {
setVal(val => {
// modify based on provided val on arrow function
return modifiedValuesArray
});
}
}, []);
Whenever you need to update the state using its current value, you need to send in a function instead to the state setter which would give you the updated current value of the state as a param. So in your case it would be something like below:
const [val, setVal ] = useState([...]);
useEffect(() => {
anotherArr.forEach(i => {
setVal((currVal) => {
let modifiedValuesArray = [];
// your update logic here (use currVal instead of val)
return modifiedValuesArray;
});
});
}, []);
Use setVal only once, not during an forEach loop. setState is async so you can't depend on it like its synchronous. In your example setVal will actually be executed some time in the future.. Do you maybe have a codesandbox example?
EDIT: You don't get updated state on "next round". setState will be executed N times, and will put it in an update queue, and React will probably only update the last setState value for optimisation. Also, your example useEffect will run only once..
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]);