Implement Component State Reducers

Kent C. Dodds
InstructorKent C. Dodds

Share this video with your friends

Send Tweet
Published 4 years ago
Updated a year ago

Often with reusable components, the logic needs to be adjusted to handle various use cases. Rather than filling our component event handlers with if statements and loading our state with one-off properties, we can expose our state directly to users of our reusable component in a way that's flexible and simple with a state reducer.

Instructor: [00:00] Our toggles render prop API does a great job of giving complete control over the rendering of the toggle component. What about state? In this situation, we want to be able to keep track of how many times the toggle button is clicked.

[00:13] When it was clicked more than four times, we want to say, whoa, you clicked too much, and we want to prevent the toggle from being toggled on until the user clicks reset again. For our usage example in this one, we keep track of the times clicked in our state, and every time the user clicks on the toggle button, we'll increment that times clicked.

[00:31] Then when they click on reset, we'll reset our states to the initial state times clicked is zero, and then in our render method, if the times clicked is greater than four, than we're going to render out this text, otherwise we'll render out the click count.

[00:45] What we need to do is expose a hook into this set state mechanism for our toggle component, so that before we call set state in the toggle component, the consumer has a chance to modify the state we're about to set.

[00:57] We can call this a state reducer, and we'll pass in the state and the changes, and then the state reducer will be responsible for returning the changes that should take place. Let's go ahead and see how we can implement this.

[01:10] I'm going to create a function called internal set state. This will simulate the same API as set state, so it'll accept the changes and a call back. Then it's going to call this.set state and it's going to get the current state.

[01:25] Then, because the changes can be a function, we need to get the changes object. We'll say const changes object equals type of changes function. If it is a function, then we'll call changes with the state, otherwise it'll just be the changes.

[01:42] Next, we can pass this to our state reducer for the state and the changes object. This is going to give us our reduced changes. That is what we're going to return, reduced changes. And then we'll call the call back.

[01:58] Finally, anytime we call set state, we'll instead call internal set state, and with that, we can toggle and then we can stop the toggling until we click reset and we get toggling again. This is the basic state reducer pattern. It allows users of your component to customize how the state is managed within the component.

[02:18] This gives users incredible flexibility over how the state is managed inside your component. If we wanted to implement this kind of functionality without the state reducer pattern, it would require a new prop specific to this use case and wouldn't be nearly as flexible as the state reducer pattern.

[02:33] In review, what we had to do to support this use case, is we created this internal set state functions that accepts the changes in call back, the same API as the normal set state API. Then we call set state to get our current state, and based on that, we get the changes object.

[02:48] Then we call the state reducer to get the reduced changes from the state and the changes object, and that is what we return. Then we forward along the call back to set state. Then anywhere inside of our component, we call internal set state to make sure that any of the changes that the internal set state method wants to make, are first passed through the state reducer.

[03:09] Then users of our component can pass a state producer. That state reducer accepts the state and the changes, and then they can return the state that they want to have updated.

[03:19] This state reducer's saying if the times clicked is greater than or equal to four, then I'm fine accepting all the changes that you want to make toggle component, but I want to make sure that on will stay false, otherwise we'll just accept any changes that are coming.

Viktor Soroka
Viktor Soroka
~ 4 years ago

Nice. Why do you need state argument in the toggleStateReducer if you do not use it?

Kent C. Dodds
Kent C. Doddsinstructor
~ 4 years ago

Hi Viktor, The Toggle example that we're using in this course is great, but it's not entirely practical. In a more real-world component, having the state can be useful. For example, some of the examples here use a state reducer (you can see the state reducers in the shared.js file) and one of them references the state: https://codesandbox.io/s/924217jvro

Viktor Soroka
Viktor Soroka
~ 4 years ago

Thanks for that.

Thein
Thein
~ 3 years ago

state argument in the toggleState reducer is called from toggle Component and not useful for Switch Component. The properties associated in that state are from toggle component.

Bogdan Bivolaru
Bogdan Bivolaru
~ 3 years ago

Hi Kent, Could you explain where timesClicked is stored - console does not print this somehow? I am trying to show timesClicked in console from handleToggle:

handleToggle = (...args) => {
    this.setState(mystate => {
      console.log(mystate)
      let {timesClicked} = mystate
      return {
        timesClicked: timesClicked + 1,
      }
    })
    this.props.onToggle(...args)
  }```
 (https://codesandbox.io/s/rwp5z49qo4)
Kent C. Dodds
Kent C. Doddsinstructor
~ 3 years ago

console does not print this somehow?

It seems to be printing it fine for me. You can learn more about the state updater function in the setState docs. I hope that's helpful.

Bogdan Bivolaru
Bogdan Bivolaru
~ 3 years ago

Thanks for your input, I'm not sure what I did the other day that broke things.

Matthias Hryniszak
Matthias Hryniszak
~ 3 years ago

You know, I may be somehow hindered but it all seems overly complex to do something so simple. I know react is all about functional programming (and this might be my disability to do FP proficiently) but still - introducing a new API for setting the state seems like the worst thing to do because it defies the least-surprise principle.