Passing a variable to the React useCallback hook - reactjs

I'm having a difficult time learning React hooks.
I'm following along on this website, https://reactjs.org/docs/hooks-reference.html
const onGameOver = React.useCallback(
({ playerScore, playerHealth, gameId }) => {
setPages(player =>
arrayMove(playerScore, playerHealth, gameId)
);
console.log('gameId: ', gameId);
},
[player, gameId]
);
I can see the playerScore and the playerHealth, but not gameId.
I put 'gameId' in my dependency array, but it's always 'undefined' in the console.log.
For testing purposes, I'm just giving gameId a dummy ID like this:
const gameId = useState(123);
But eventually, I will use it like this:
<GameOverScreen controlId={ControlId} stats={endGameStats} onGameOver=({onGameOver, gameId}) />
What could I be doing wrong?
Thanks

The gameId in the dependencies array is not the same value inside the function when it is invoked. This is because your function definition destructures gameId out of the first argument passed to it:
vvvvvv
const onGameOver = React.useCallback(({ playerScore, playerHealth, gameId }) => {
This will "shadow" the value of gameId outside of the function passed to React.useCallback().
The dependencies array passed to React.useCallback() are not implicitly passed to the function being created. The array is used to determine whether or not the function passed to React.useCallback() on that particular render should replace the function memoized by React* - Remember, React.useCallback() is roughly equivalent to:
React.useMemo(() => f, deps)
You will either have to pass gameId to onGameOver when it is executed, like this:
onGameOver({ gameId: .... })
Or you will need to remove gameId from the destructuring assignment:
const onGameOver = React.useCallback(({ playerScore, playerHealth }) => {
The latter is probably the correct approach, since this way onGameOver will always have the correct value for gameId without having the callers needing to know about it.
* The dependency array is necessary because the hooks themselves are called on every render, but we may want to keep some values stable across different renders.
Each render, each element in the deps array is compared with the deps array from the previous render. If any of them have changed, then the hook is marked as 'stale' and some kind of effect will take place, depending on the hook:
useMemo(f, deps) will execute the function f and the return value of that function will be provided as the return value of useMemo() on this current render and subsequent renders until deps changes again.
useCallback() is a wrapper around useMemo() that is slightly easier to use when intending to memoize a function. useCallback(f, deps) is equivalent to useMemo(() => f, deps).
useEffect(f, deps) and useLayoutEffect(f, deps) will both execute f when the dependency array changes, although when these functions are executed will differ based on which hook you use. You should use useLayoutEffect() if you need to interact with the DOM, otherwise you should use useEffect().
This is why an empty array in place of the deps array will lead to an effect only being executed once for the components lifecycle - Because the arrays values will never change, so the effect will never be re-run.

Updating in response to your edits:
You added const gameId = useState(123); but that isn't quite right. useState returns an array with the state value, and a function that can be called to update that state. Typically you should do this:
const [gameId, setGameId] = useState(123)
And then you added this:
<GameOverScreen ... onGameOver=({onGameOver, gameId}) />
But that isn't valid JSX. To set a prop on a component, you either need to put it in quotes like <MyComponent message="Hello"/> or in curly braces like <MyComponent gameId={gameId}/>. Parentheses don't work. Also, I'm not sure what you're trying to do with a value like {onGameOver, gameId}... If you are trying to pass those two things as props, it should be more like
<GameOverScreen ... onGameOver={onGameOver} gameId={gameId} />
Original Answer
Imagine your function in isolation:
function actualOnGameOver({ playerScore, playerHealth, gameId }) {
setPages(player =>
arrayMove(playerScore, playerHealth, gameId)
);
console.log('gameId: ', gameId);
}
Look at all of the things that are referenced by the function, and determine where they come from:
setPages - from outside the function (I'm guessing from a useState call)
arrayMove - from outside the function (I'm guessing from an import)
playerScore - received as an argument
playerHealth - received as an argument
gameId - received as an argument
Note that useCallback is a convenience version of useMemo, i.e. it's making/getting a cached version of your function. The dependencies array is used to tell React when it should invalidate that cache. Any non-constant values referenced by the function that come from outside the function should be mentioned in that array. Values received as arguments to the function (e.g. gameId) shouldn't go in there.
So your dependencies array should be [setPages] (or [setPages, arrayMove] if my guess about arrayMove being an import was wrong) because that is the only non-constant value referenced by the function that wasn't passed in as an argument.
When you pass the actualOnGameOver into useCallback, the result is a function with the same signature, so you would call onGameOver the same way, e.g.
onGameOver({
playerScore: 100,
playerHealth: 75,
gameId: 'abc123'
})
Once you fix your dependency array, if gameId is still undefined, you should look into the code outside of your onGameOver function. Make sure when you call onGameOver you are passing a value for gameId.

It is strange to see gameId into useCallback deps array. I think you dont have to put on it the function parameters, but only variables (props or useState values) used inside the definition of the function.
To answer to your question it depends of the params passing on onGameOver function when you call it.

Related

Function rendering multiple times. UseCallback React

I am trying useCallback for first time. I am not able to see any changes from earlier output in terms of function re rendering.
My function structure will be
const myfunction = ( array1, array2) => {
//doing a very big calcuation
}
what I tried to use is
const getCards = useCallback((a,b) => myfunction(a,b),[myfunction])
I tried to to pass a,b in dependencies of Usecallback which gives me parameters not found error.
I am passing getCards into the component like this
<Component summaryCallback={getCards}>
I am trying to reduce the number of call inside my function. I want to call this function only if there is a change in array1 or array2 from my previous rendering
Thanks in advance
All hooks are stored with their respective dependencies. If you use a variable that's not included as a dependency to a hook, their initial value is used.
Adding it to the dependency list will enforce the update of the hook and therefore all data inside the hook.
Either way, you might not at all use useCallback if you've got large/heavy computations. You might consider useMemo for it. Ref: https://reactjs.org/docs/hooks-reference.html#usememo
You might use it like the following:
const cards = useMemo(() => { /* heavy computation */ }, [depA, depB]);
return <CardList cards={cards} />
It rerenders everythime, you're dependencies depA or depB change.

How does useMemo() create an id for its factories?

There is a function in React called useMemo()
const eg = React.useMemo(() => true, []);
If I included the same function twice, how would it know which memoization to return?
const eg1 = React.useMemo(() => Math.random(), []);
const eg2 = React.useMemo(() => Math.random(), []);
There's no explicit ID for either of these arrow functions, and -- that I know of -- there is no way to ID them except to perhaps hash their inner text. In this case, they'd both have the same ID.
What kind of funky magic is going on here?
React knows which is which because it makes the assumption that you will always call useMemo exactly the same number of times, in exactly the same order. Thus, it knows that the 1st time you call useMemo during a render, it should check the dependency array from the 1st call last time, and return the 1st value. Then the second time you call useMemo, it checks the 2nd dependency array and returns the 2nd value. Etc.
That's why the rules of hooks insist that you can't call hooks conditionally.
From what I understand of useMemo, it just won't be able to memorize and will always recalculate the values due to lack of parameters.
useMemo avoids calculations if function parameters are the same, if there are no parameters, a new value will be calculated on each rendering.

When React functional component re-render, does it reassign assigned values & functions?

If a code like this re-render by useEffect's dependency,
// ...
const Test = () => {
// ...
const value1 = "test1"
const func1 = () => {
// do something1
}
useEffect(() => {
const value2 = "test2"
const func2 = () => {
// do something2
}
}, [sth])
return (
// ...
)
}
does value1 & value2 & func1 & func2 reassign to memory?
I'm curious about it, related to optimizing.
Short answer, yes. Every time the function runs the old values will be garbage-collected, new primitive values will be assigned in memory and new references will be created to functions and objects.
But the real question with a "not-that-short" answer is "does it impact performance in a significant way?". And the answer is... depends. In most cases, it will not (see the doc about it). But there are scenarios where you will need to make some changes to have a performance optimization with useCallback and use useMemo.
It's also worth mentioning (as said in Shivam Jha's answer) that a trigger in useEffect no necessarily causes a re-render (DOM paint) because this process happens first on the virtual DOM and will only be persisted in the real DOM when necessary.
I will leave here some other references about this discussion.
Dan Abramov's tweet on memoizing everything (also look at responses)
Kent C. Dodds's article about render performance
Felix Gerschau's article about when render occurs
does value1 & value2 & func1 & func2 reassign to memory?
in short the answer is yes.
it's more clear if you look at it for what it is: Test is a function, so everytime that function is called all the variables inside the function scope (the curly brackets) are re-declared and re-assigned.
Let's dive into the details:
value1 and func1 are in the function's body so they get declared and assigned every time the function is called, they are not related at all, they have just the same name.
value2 and func2 instead are declared inside an useEffect hook with a declared dependency (sth), this means that these 2 variables are redeclared and reassigned only after the first render and after every other render if the sth variable changed its value compared to the previous render.
if you want to optimize value1 so it doesn't change at every render you can use the useMemo hook this way:
const value1 = React.useMemo(() => {
return "test1"; //here you might have a more complicate way to determine value1
}, []); //array of dependencies like for `useEffect`, so `value1` will be recalculated only if any of the values provided in here change. by leaving it empty value1 will always be the **same** variable
you can do similar optimizations with functions too with the useCallback hook
According to docs:
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.
Also, it does not re-renders the code but runs the code again when the dependencies passed to it changes
Tip: Optimizing Performance by Skipping Effects describes solving performance problem due tocleaning up or applying the effect after every render.
Also, you can free up allocated memory (if not freed automatically) or run some side effects after running the code (setTimeOut, etc) by using useEffect with cleanup.
Basically do everything you want to run after useEffect inside a return function:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});

Is it more performant to use the dependencies of a React `useCallback` hook as arguments of the callback?

Suppose I have a callback function in React which uses variables a, b, and c. I want to know the best way to make use of the useCallback hook.
I can create a callback which takes no arguments and use [a,b,c] as the dependencies array.
const onMutate = useCallback( () => {~~~~} , [a,b,c] )
~~~~ onPress={onMutate}
Or I can create a callback which takes arguments (a,b,c) and has an empty dependency array.
const onMutate = useCallback( (a,b,c) => { ~~~~} , [] )
~~~~ onPress={() => onMutate(a,b,c)}
In the former, the callback function is newly created each time, but there is no anonymous function in onPress.
In the latter, the callback function is created only once, but an anonymous function is created in onPress each time.
Which case is better?
First keep in mind: Using useCallback is not always a good case, as Kent C. Dodds says.
However, if you really wanna go with useCallback it also only makes sense, when the value in useCallback is memoized. See this one CodeSandbox: https://codesandbox.io/s/quizzical-dawn-glqlg
That being said, if you are sure to use useCallback it doesn't matter, which way you are going, because in the very best case, all three of those variables are being memoized in the first run.

useEffect pushing value to array only once ReactJs

So basically i started learning ReactJs and am trying to build a chatting app
now here there is section of adding Channels so here is my code
const [Channels, setChannelState] = React.useState([])
let loadedChannels = []
React.useEffect(() => {
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
console.log(loadedChannels)
setChannelState(loadedChannels)
})
},[])
so here when i tried to log the loadedChannels Array am getting all the result but when i try to set this array to my state then am getting only one result
how can i resolve this?
You have an empty array [] as a second parameter which runs the function passed to useEffect only once.
You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect which you have done.
If we pass [] as second parameter this will apply effect only once.
We can pass value inside array, only on change which it will trigger useEffect call.
As per the documentation
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
try using something like this:
const [Channels, setChannelState] = React.useState([])
React.useEffect(() => {
let loadedChannels = []
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
})
setChannelState([...loadedChannels])
},[])
Hope this helps, Happy coding!!!
The second argument for useEffect is called a dependency array. And when it's empty, it acts like componentDidMount() i.e it runs only once, after the first render.
You also need to be careful about setting state in useEffect, since this might create an infinite loop of re-renders if not done correctly - setCurrentUser. That is not enough code to help any further than that.
This is the perfect time to get into React. Have fun!
Second parameter is passed for conditional rendering. To understand that there are few scenarios which tell us the possible ways to use that.
There are multiple scenarios which will make it easier for you to understand the importance and functionality of the second parameter with the help of the example from demo.
Scenario 1
Second parameter as empty array,
useEffect(() => {
console.log(`You clicked ${count} times`);
},
[ ]) //called only once similar to componentDidMount
If we pass an empty array, it means that we are not interested in monitoring any of the values so that the useEffect won’t be called except on mounting and before un-mounting. This mimic’s the working of componentDidMount and componentWillUnmount, it will only run once.
Scenario 2
Second parameter with value(s),
useEffect(() => {
console.log(`You clicked ${count} times`);
}, [count]); //function inside useEffect will be called when "count" will be updated
The value passed as the second parameter (array) here in our example: count will be responsible for the execution of the function inside the useEffect Hook. If the value inside the array will be updated, then and only then the function will be executed.
What if we have multiple useEffect hooks and we need to update just few of them? Conditional rendering comes in light. Conditional rendering is achieved by passing the second parameter instead of just empty array. The values in the array will be monitored and the useEffect function will only be called when the monitored value(s) is/are updated.
Refer these links for more info
https://stackblitz.com/edit/react-hooks-intro
https://www.c-sharpcorner.com/article/react-hooks-introduction/

Resources