When to use Control Props or State Reducers
You’ve probably used components or elements that implement the control props pattern. For example:
<input onchange="{this.handleInputChange}" value="{this.state.inputValue}"/>
Read more about the concept of control props in the react docs.
You may not have had much experience with the idea of a state reducer. In contrast to control props, built-in react elements don’t support state reducers (though I hear that reason-react does). My library downshift supports a state reducer. Here’s an example of using it to prevent the menu from closing after an item is selected:
function stateReducer(state, changes) { if (changes.type === Downshift.stateChangeTypes.clickItem) { // when the user clicks an item, prevent // keep the isOpen to true return {...changes, isOpen: true} } return changes } const ui = ( <downshift statereducer="{stateReducer}"> {() => <div>{/* some ui stuff */}</div>} </downshift> )
You can learn how to implement these patterns from my Advanced React Component Patterns material.
Both of these patterns help you expose state management to component consumers and while they have significantly different APIs, they allow much of the same capabilities. So today I’d like to answer the question I’ve gotten many times which is: “When should I expose a state reducer or a control prop?”
Control Props are objectively more powerful because they allow complete control over state from outside the component. Let’s take my favorite Toggle component as an example:
class Example extends React.Component { state = {on: false, inputValue: 'off'} handleToggle = on => { this.setState({on, inputValue: on ? 'on' : 'off'}) } handleChange = ({target: {value}}) => { if (value === 'on') { this.setState({on: true}) } else if (value === 'off') { this.setState({on: false}) } this.setState({inputValue: value}) } render() { const {on} = this.state return ( <div> {/* here we're using the `value` control prop exposed by the <input/> component */} <input onchange="{this.handleChange}" value="{this.state.inputValue}"/> {/* here we're using the `on` control prop exposed by the <toggle></toggle> component. */} <toggle on="{on}" ontoggle="{this.handleToggle}"></toggle> </div> ) } }
Here’s a rendered version of this component:
As you can see, I can control the state of the toggle button by changing the text of the input component, and control the state of the input by clicking on the toggle. This is powerful because it allows me to have complete control over the state of these components.
Control props doe come with a cost however. They require that the consumer completely manage state themselves which means the consumer must have a class component with state and change handlers to update that state.
State reducers do not have to manage the component’s state themselves (though they can manage some of their own state as needed). Here’s an example of using a state reducer:
class Example extends React.Component { initialState = {timesClicked: 0} state = this.initialState handleToggle = (...args) => { this.setState(({timesClicked}) => ({ timesClicked: timesClicked + 1, })) } handleReset = (...args) => { this.setState(this.initialState) } toggleStateReducer = (state, changes) => { if (this.state.timesClicked >= 4) { return {...changes, on: false} } return changes } render() { const {timesClicked} = this.state return ( <div> <toggle onreset="{this.handleReset}" ontoggle="{this.handleToggle}" statereducer="{this.toggleStateReducer}"></toggle> {timesClicked > 4 ? ( <div> Whoa, you clicked too much! <br/> </div> ) : ( <div>Click count: {timesClicked}</div> )} </div> ) } }
And here’s a gif of the rendered interaction.
Now, you could definitely implement this experience using a control prop, but I would argue that it’s a fair bit simpler if you can use the state reducer. The biggest limitation of a state reducer is that it’s impossible to set state of the component from outside it’s normal setState
calls (I couldn’t implement the first example using a state reducer).
I hope this is helpful! Feel free to see the implementation and play around with things in this codesandbox
Good luck!
Looking for a job? Looking for a developer? Check out my job board: kcd.im/jobs
Learn more about React from me:
- egghead.io (beginners) - My Beginner’s Guide to React absolutely free on egghead.io.
- egghead.io (advanced) - My Advanced React Component Patterns course available on egghead.io today!
- Frontend Masters - My Advanced React Patterns workshop
- Workshop.me - I’m giving my Advanced Component Patterns workshop in person in Portland in July!
- Workshop.me - I’m giving my Intro to React workshop in person in Salt Lake City in August!
Things to not miss:
My friend Jamund and his wife Kari have been working on a conference to bring awareness of mental health issues in tech. The lineup looks amazing. Use the code TECH for $25 off. www.anxietytech.com
- DevTips with Kent ❗️❗️❗️ I’ve started a new series of daily short videos about software development. I livestream them every weekday. Check out the playlist of videos I have up there already including npm tips, TDD with react-testing-library, webpack HMR, and more!
- A bitter guide to open source by Ken Wheeler. It’s incredibly insightful (and full of cursing, you’ve been warned).
Some tweets from this last week:
> Ok, I’m all in. Using Night Owl 🦉 (http://aka.ms/nightowl ) by @sarah_edo, I bought Dank Mono 🤘 (http://dank.sh ) by @_philpl, and I enabled font ligatures so I too can be cool 🕶 – 24 May 2018
> Looking for feedback and ideas about react-testing-library 🐐 adoption > > - If you haven’t tried it yet, why? > - If you have tried it and don’t plan to use it, why? > - If you have tried it and use it or plan to, could you make a video/talk/blog post about your experience? > > RT Plz thx – 25 May 2018
This week’s blog post is “React DevTools”. It’s the published version of my newsletter from 2 weeks ago. If you thought it was good, go ahead and give it some claps (👏x50) and a retweet:
Special thanks to my sponsor Patreons: Hashnode
P.S. If you like this, make sure to subscribe, follow me on twitter, buy me lunch, support me on patreon, and share this with your friends 😀
👋 Hi! I’m Kent C. Dodds. I work at PayPal as a full stack JavaScript engineer. I represent PayPal on the TC39. I’m actively involved in the open source community. I’m an instructor on egghead.io, Frontend Masters, and Workshop.me. I’m also a Google Developer Expert. I’m happily married and the father of four kids. I like my family, code, JavaScript, and React.