Our class-based component relies on instance variables to ensure it's only created once.
this.amountChanged = debounce(this.props.changeAmount, 500);
Functional components aren't classes and therefore don't have instance variables. Because of that it might seem like a good idea to write your component like this:
// BAD EXAMPLE
const onAmountChanged = debounce(changeAmount, 500);
This has the problem of executing the debounce
function on each render, creating many new onAmountChanged
functions. Instead we're going to use the useMemo
hook, which will only execute the debounce method when the dependencies change, which in this case is just on first render.
const onAmountChanged = useMemo(() => debounce(changeAmount, 500), []);
Lastly we note the warning that we did not include a dependency in our method. This is a friendly lint warning that in practice doesn't always need to be heeded. However we end up adding the changeAmount
prop to our dependencies array, just in case the prop ends up changing, so we get an updated onAmountChanged
varible.
Our final debounced method ends up looking like this:
const onAmountChanged = useMemo(() => debounce(changeAmount, 500), [changeAmount]);
The "memo" in useMemo
is short for memoization. Memoization is a programming technique for making code more efficient by remembering past values. You can learn more about it here:
https://reactjs.org/docs/hooks-reference.html#usememo
Jamund Ferguson: [0:04] Inside of AmountField.js, import useMemo alongside useState, and just below our useState call, type const onAmountChanged = useMemo, and for the arguments pass in an arrow function that returns debounce and then changeAmount, which is a prop, comma 500. Outside of that, comma, empty array.
[0:21] You'll see that our debounce function here mirrors very closely what we had before in our constructor, debounce(this.props.changeAmount, 500), and debounce(changeAmount, 500). It's the same thing, except this time it's wrapped inside a useMemo function instead of being saved as an instance variable.
[0:39] UseMemo will call this function anytime these dependencies change, and it'll return the value of this function and place it in the onAmountChanged variable. In our case, debounce(changeAmount, 500) is going to be called only one time, the first time that this component is rendered, and that value will be stored in the onAmountChanged variable.
[1:03] We can now get rid of our constructor, save the file, and finally replace this.changeAmount(newAmount) with our new onAmountChanged debounced function. Now, as I type in any changes, you'll see that after about half a second, the Rates Table gets updated with the new values.
[1:20] We've fully migrated the AmountField over from being a class component to a function component. We put the state as useState and instead of storing our debounced method as an instance variable, we now used the useMemo function to make sure it's created only once.
[1:36] If you go into the console, you'll see one error with the way that we're using useMemo here. It says it has a missing dependency 'changeAmount.' It's true, changeAmount is a prop and therefore, could potentially change. This useMemo call right now will never change, even if the changeAmount function changes.
[1:54] You will sometimes see a Lint warning that says you should put changeAmount in here. Even though in our case it won't make a difference, it's probably best to practice to do so.