Does React.useCallback memoize curried functions? - javascript

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).

Related

Execute useEffect() only on specific setState()

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>;
}

What is correct way to call function inside setState(useState) hooks

I want to update state at real time and call function inside setState but i am getting error.
onClick={() => setDetails(prevState => ({...prevState,id: product.orderId,status: 'processing'}) => updateStatus() )}
Can someone tell me correct syntax and expression
It seems you are using hooks. You can't call a function in setState like in class components. You need to watch it through useEffect.
React.useEffect(() => {
updateStatus()
}, [state.id, state.status])
the function created using useState hook doest have a second argument. it will not support call back function after updating the state. it was only there in the setState function which is used in class components.
You can use useEffect instead
useEffect(()=> {
updateStatus()
}, [details])
return (
...
onClick={() => setDetails(prevState => ({...prevState,id: product.orderId,status: 'processing'}))}
...
)
in this way, on every setDetails update it will call useEffect and then updateStatus

Is there a way to define a hook in a for?

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.

React hooks: callback as prop re-render every hooks depending on it when it's called

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?

How are different function signatures differentiated in setState function returned by `React.useState`?

I'm new to JavaScript and React.
I was reading from React's documentation https://reactjs.org/docs/hooks-reference.html#functional-updates
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
And was curious how different function signatures are being handled in setState (here setCount) function returned by React.useState.
Since JavaScript is a dynamically typed language and therefore function overloading does not exist, my initial guess was that there's only a single function that simulates the behavior of function overloading, such as in this question Function overloading in Javascript - Best practices.
Or is this handled in some different way by React?
EDIT: I think this question could be also applied to the React.useState function itself https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
my initial guess was that there's only a single function that simulates the behavior of function overloading
Yes, in javascript if you want a function to take a variety of different parameters, you write a single function and then write code that checks what was passed in. For example:
const example = (val) => {
if (typeof val === 'function') {
// do the function behavior
} else {
// do the other behavior
}
}
EDIT: since you want to know the code that react specifically uses, here goes. In short, it does the same thing i just answered, but with more steps to get there.
The starting point for your expedition through the code is this file. This is the function you call when you call useState. However, all it does is turn around and ask "dispatcher" to do useState.
The "dispatcher" is a way to handle the fact that there are several different ways react can be used, and they sometimes need different implementations of hooks. For example, you might be rendering on the server, or on the dom, or on react native, or in a custom renderer.
Finding those dispatchers in react's codebase is going to be tricky. They're spread all over the place in different packages, since it's only those packages that need the specific implementations.
Additionally, when you find an implementation, it's going to have a lot of reuse of functions, since they do similar things. useState is just a special case of useReducer, so it's going to reuse useReducer as a way to implement useState.
All that to say that when you call setState, here is one example of the places the code could end up:
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
The action param is the value or function you passed into setState. If it's a function, it will call it and pass in the most recent state, then return whatever your function returns. If it's not a function, it will just return what you passed in. This return value then becomes the new state.

Categories