What's Going to Happen to React Context?
With the cool new stuff coming to React (Hooks/Suspense), what’s going to happen to the context api?
Earlier this year, the React team introduced the first official context API. I blogged about that new API and people got sufficiently and reasonably hyped.
One common complaint that I knew people were going to have when applying it practically was the fact that the context consumer is a render-prop based API. This can lead to a lot of nesting when you need to consume multiple contexts and other render-prop based APIs as well (for logic reuse). So I addressed that in the blog post by suggesting that you could combine all of the render-prop based APIs into a single function component and consume that:
const ThemeContext = React.createContext('light') class ThemeProvider extends React.Component {/* code */} const ThemeConsumer = ThemeContext.Consumer const LanguageContext = React.createContext('en') class LanguageProvider extends React.Component {/* code */} const LanguageConsumer = LanguageContext.Consumer function AppProviders({children}) { return ( <LanguageProvider> <ThemeProvider> {children} </ThemeProvider> </LanguageProvider> ) } function ThemeAndLanguageConsumer({children}) { return ( <LanguageConsumer> {language => ( <ThemeConsumer> {theme => children({language, theme})} </ThemeConsumer> )} </LanguageConsumer> ) } function App() { return ( <AppProviders> <ThemeAndLanguageConsumer> {({theme, language}) => <div>{theme} and {language}</div>} </ThemeAndLanguageConsumer> </AppProviders> ) }
As much as this solution works thanks to the composability of React components, I’m still not super thrilled with it. And I’m not the only one:
We’ve heard feedback that adopting the new render prop API can be difficult in class components. So we’ve added a convenience API to consume a context value from within a class component. – React v16.6.0: lazy, memo and contextType
This new convenience API means that if you use a class component and you’re only consuming one context, you can simply define a static property called contextType
and assign it to the context you want to consume, then you can access the context via this.context
. It’s pretty neat and a nice trick for common cases where you only consume a single context.
I’ve used this convenience API and I love it. But I’m even more excited about the implications that React Hooks have for the future of React context. Let’s rewrite what we have above with the upcoming (ALPHA!) useContext
hook:
const ThemeContext = React.createContext('light') class ThemeProvider extends React.Component {/* code */} const LanguageContext = React.createContext('en') class LanguageProvider extends React.Component {/* code */} function AppProviders({children}) { return ( <LanguageProvider> <ThemeProvider> {children} </ThemeProvider> </LanguageProvider> ) } function App() { const theme = useContext(ThemeContext) const language = useContext(LanguageContext) return ( <AppProviders> <div>{theme} and {language}</div> </AppProviders> ) }
WOWZA! As powerful as the render-prop based consumers are, this is even easier to read, understand, refactor, and maintain! And it’s not just less code for less code’s sake. Besides, often when we reduce the amount of code we also reduce the clarity of communication that code can give to us. But in this case, it’s less code and it’s easier to understand. I think that’s a big win and a huge feature of the new hooks API.
Another big feature of React hooks is the fact that it’s completely opt-in and backward compatible. I’m given such a huge amount of comfort knowing that Facebook can’t make decisions that will cause grief to the engineers who are working on the oldest and one of the largest React codebases in the world. The fact that React has incrementally taken us to this new world of hooks is just fantastic. Thanks React team! Looking forward to the official release!
Conclusion
One of the coolest things about React is that it allows us to focus on solving real-world problems without normally having to get too close to the implementation of things. It’s been a long time since I had to deal with cross-browser or performance issues with any degree of regularity. And now React is taking it even further and simplifying things so the code that I do write is simpler to read, understand refactor, and maintain. I just love that. Makes me wonder if there may be some things I could do about my code to simplify things for other people as well 🤔.
Until next time! Good luck! 👋
Bonus: Introducing a new course: Simplify React Apps with React Hooks and Suspense
Learn about the massive improvements coming to function components in React via a fresh new course showing you how to refactor an existing app to these new and upcoming APIs.
Ok, before I get into things, can I just say that this course artwork by Maggie Appleton and Maxime Bourgeois is just the absolute best. It’s just so good. 😍
I’m super excited to share this course with you. I’ve been using React full time for almost three years now and I’ve never been more excited (!!) about writing components than when I started playing around with Hooks and Suspense. Let’s get a quick rundown of what you can expect from the course:
About the course
With the massive improvements to function components in React via hooks and suspense, you may be interested in seeing how to refactor a typical class component to a simpler class component that uses React Suspense and Hooks features. In this course, Kent will take a modern React codebase that uses classes and refactor the entire thing to use function components as much as possible. We’ll look at state, side effects, async code, caching, and more!
Want a primer on hooks and suspense? Watch my React Hooks and Suspense Playlist!
note: React Hooks is alpha and subject to change. The React team has the 16.x roadmap here.
Introduction to Refactoring a React Application to React Hooks and React Suspense
Let’s get a quick overview of what this course is all about and how it’s been structured to make sure you’re as productive as possible with these new features.
Refactor a Class Component with React hooks to a Function
We have a render prop based class component that allows us to make a GraphQL request with a given query string and variables and uses a GitHub graphql client that is in React context to make the request. Let’s refactor this to a function component that uses the hooks useReducer, useContext, and useEffect.
Ensure all React useEffect Effects Run Synchronously in Tests with react-testing-library
Thanks to react-testing-library
our tests are free of implementation details, so when we refactor components to hooks we generally don’t need to make any changes to our tests. However, useEffect
is slightly different from componentDidMount
in that it’s actually executed asynchronously after the render has taken place. So all of our query tests which relied on the HTTP requests being sent immediately after render are failing. Let’s use the flushEffects
utility from react-testing-library
to ensure that the pending effect callbacks are run before we make assertions.
Handle Deep Object Comparison in React’s useEffect hook with the useRef Hook
The second argument to React’s useEffect
hook is an array of dependencies for your useEffect
callback. When any value in that array changes, the effect callback is re-run. But the variables
object we’re passing to that array is created during render, so our effect will be re-run every render even if the shape of the object is the same. So let’s solve this by doing our own equality check from within the effect callback.
Safely setState on a Mounted React Component through the useEffect Hook
In the class version of this component, we had a method called safeSetState
which would check whether the component was still mounted before trying to call setState
. This is because our graphql client library is unable to cancel in-flight requests. Let’s make that same kind of thing work by tracking the mounted state of our component using the useRef
and useEffect
hooks.
Extract Generic React Hook Code into Custom React Hooks
Because hooks code is regular JavaScript, extracting it to its own function is trivial and enables code sharing in a really nice way. It also allows us to encapsulate and separate concerns really cleanly. Custom hooks also compose really nicely together to build more complex hooks out of more primitive ones. Let’s do this by creating a useSetState
and useSafeSetState
custom hook.
If you would like a more comprehensive useSetState
hook, give use-legacy-state
a try.
Track Values Over the Course of Renders with React useRef in a Custom usePrevious Hook
Our hook to track the previous values looks pretty useful, so let’s extract that into it’s own custom React Hook called usePrevious
.
Deeply Compare Inputs in a Custom React Hook for useEffect
It would be nice if useEffect
did the deep value comparison for us. Why don’t we make our own custom hook that does that for us? In this lesson we’ll create a useDeepCompareEffect
which will allow us to use it just like a useEffect
and allow us to just pass the inputs.
Refactor a React Class Component with useContext and useState Hooks
We’ve got a pretty simple User class component that manages a bit of state and uses some context. Let’s refactor this over to a function component that uses the useContext
and useState
hooks.
Refactor a render Prop Component to a Custom React Hook
Our <Query />
component is a render prop based component that the <User />
component uses. But because it doesn’t render anything, we can actually just change it to a custom hook. Let’s create a useQuery
hook that returns the state from the hooks the Query component uses and use that instead. But we’ll preserve the component so we don’t have to refactor everywhere that uses the Query render prop based component as well and we can keep our tests passing as they are.
Handle componentDidMount and componentWillUnmount in React Component Refactor to Hooks
Let’s refactor our GitHubClientProvider
class component to a function component that uses hooks. This one’s pretty interesting because we can use useEffect
to encapsulate everything we need for a single effect, truly separating that concern within our component.
Dynamically Import React Components with React.lazy and Suspense
With React 16.6.0, React Suspense was officially released as a stable feature (with limited support for React.lazy
). Let’s refactor our lazily-loaded components that are using react-loadable
to components that use the built-in React.lazy
feature.
Preload React Components with the useEffect Hook
While users are filling out the form on our home page, it would be a good idea to pre-load the next page they will be going to so they don’t have to wait for it to load once they’ve finished filling out the form. React’s useEffect
hook makes this really easy.
I hope you enjoy the course! It’s free for a limited time. You should also know that egghead.io is putting on it’s biggest sale of the year: 45% off a year-long membership! How cool is that!?
Things to not miss:
- Simplify React Apps with React Hooks and Suspense - My new egghead course… of course!
- Shurlan - I WON NANOWRIMO THIS WEEK! That means that I successfully wrote 50,000 words of a novel in the month of November (for perspective, Harry Potter book 1 is 76k words). It was a wild month, and it was tons of fun. And you can read what I ended up with. It’s a fantasy novel about a utopian world where things start to go bad and a 14-year-old girl is tasked with stopping a rebellion from inadvertently destroying the city. I think you’ll love the characters, plot, and magic system :)
- React 16.x Roadmap - Tl;DR: React 16.6: Suspense for Code Splitting (already shipped), React 16.7: React Hooks (~Q1 2019), React 16.8: Concurrent Mode (~Q2 2019), React 16.9: Suspense for Data Fetching (~mid 2019)
- Modern React Workshop: Hooks & Suspense - a recording of a livestream I did last week at PayPal. Here’s the workshop repo and here’s the part 2.
Some tweets from this last week:
My nephew is 9, interested in computers/coding, and my brother is wondering what Christmas gift would be appropriate for him. Wondering if a raspberry pi would be good or if there’s something else that would be better for a 9 year old. Thoughts?
I would say about 50% of my open source PR merges happen on my phone. 📱
This week’s blog post is “How Gratitude can make you a better developer”. 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:
I just published “How Gratitude can make you a better developer”
How Gratitude can make you a better developer
What does gratitude have to do with software development?
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 the creator of TestingJavaScript.com and I’m an instructor on egghead.io and Frontend Masters. 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.