React’s Context API is convenient built-in state management for React Projects. It has it’s advantages and disadvantages over a library like Redux for sending props and changing the app’s state. I’m going to focus on the advantages of using Context API and getting an overly simple example to work.
The example in this post only has two levels of components, so it is actually advised against by the React documents. If you were actually making an app with this few of components it would be easier just to pass props and lift up state. But let’s figure out how Context API works! Below is a mini React Context API app that is a light bulb and a light switch.
I’m going to assume a few things:
- a working knowledge of React.
- familiarity with the single source of truth behind the Flux model in React
- familiarity with ES6 JavaScript syntax
Also, the full code for the project is at https://github.com/seandolinar/context-api-example, but the code excerpts in the post are only the relevant parts (I left out the import statements, etc.). I made it with create-react-app
, which is a pretty good quick start boilerplate for playing around with React without a lot of setup time.
Alright! There are really just three normal React components:
- the
App
- the
LightBulb
- the
LightSwitch
The app’s global state only has the lightbulb on/off, which affects only the display of the LightBulb
. And that state can be changed by only the LightSwitch
. Overly simple; let’s go through some code!
context.jsx 📄
export default React.createContext()
Alright, this is too simple. We create a context that can be imported into the different the components.
app.js 📄
import LightCircuit from './context/context' class App extends Component { constructor(props){ super(props) this.state = {on: false} } render() { return (this.setState({on: !this.state.on }) } }> ); } }
This object is the heart of the app, so let’s start here. We create a state in the App’s constructor, initially setting the state.on = false
. The new part is the LightCircuit.Provider
Context API component. It wraps the what would have been the root app component. The value
attribute of the Provider is the default/starting value. There are two items in the default context:
- the App’s state [
state
] - a function for changing the App’s state [
flipSwitch()
]
This will be passed to any Consumer components like in the LightBulb
component.
LightBulb.jsx 📄
import LightCircuit from './context/context' class LightBulb extends Component { render() { return{ ({state}) => } } }
This component is just a Context Consumer component, and a div that uses the context’s state. The ({state}) =>
part of the arrow function destructs the context and only uses the state, which is then used to change the className
which controls CSS to show the light bulb is lit or dark. The div that’s inside the consumer component could be any component, composition of components, or even JavaScript.
The annoying thing about Context API is having to pass the context through the arrow function. It creates some JavaScript structure I’m not crazy about since it limits what you can do with the context in that component without additional code.
LightSwitch.jsx 📄
import LightCircuit from './context/context' class LightSwitch extends Component { constructor(props){ super(props) this.state = {up: false} } render() { return{ ({flipSwitch}) => { const handleClick = () => { this.setState({up: !this.state.up}) flipSwitch() } return } } } }
The LightSwitch
component does one important thing: it runs flipSwitch()
from the context, which in turn changes the state in the top-level App component. We are destructuring the flipSwitch()
from the context then putting that into a catch all handelClick()
function to run when the button is clicked.
I did add one extra layer on complication, I create an up and down state internal to the switch. Since the circuit doesn’t affect the position of the physical switch in real life, so it shouldn’t here. That is why the extra handleClick()
function was made and why the LightSwitch
component has its own state.
Conclusion
Hopefully, walking through the simple example with Provider, Consumer and Context can help you get Context API working for your app. Again, this is too simple of a demo, and actually a bad example of how to use it. It works well if the light switch was buried within a hypothetical LightPlate, Wall, Room, and Circuit component. That way you wouldn’t have to lift up state through several layers of components.