Step 2.4 - React Context (Demo)
In this step, we describe some problems we encounter when creating a more complex application.
We will solve these problems with the React Context API. The Context API consists of:
- Provider component
- Consuming context from a Class Component
- Consuming context from a Functional Component
For a single component, React gives us a mental model like this:
(props) => view;
In a real application, these functions are composed. It looks more like this:
Problems in a Complex Application
-
Data needs to be passed down from component to component via props. Even when some components do not need to know about some data.
-
There is a lack of coordination of changes that can happen to the data
Even in our simple application, we saw this problem. For example, <TodoList> has this props interface:
interface TodoListProps {
complete: (id: string) => void;
remove: (id: string) => void;
todos: Store['todos'];
filter: FilterTypes;
edit: (id: string, label: string) => void;
}
All of these props are not used, except to be passed down to a child Component, TodoListItem:
<TodoListItem todos="{todos}" complete="{complete}" remove="{remove}" edit="{edit}" />
Context API
Let's solve the first one with the Context API. A context is a special way for React to share data from components to their descendant children components without having to explicitly pass down through props at every level of the tree.
We create a context by calling createContext() with some initial data:
const TodoContext = React.createContext();
Now that we have a TodoContext stuffed with some initial state, we will wrap TodoApp component with TodoContext.Provider so that it can provide data to all its children:
class TodoApp extends React.Component {
render() {
return (
<TodoContext.Provider
value={{
...this.state,
addTodo={this._addTodo},
setFilter={this._setFilter},
/* same goes for remove, complete, and clear */
}}>
<div>
<TodoHeader />
<TodoList />
<TodoFooter />
</div>
</TodoContext.Provider>
);
}
}
Inside the children components, like the <TodoHeader> component, the value can be access from the component's context prop like this:
class TodoHeader extends React.Component {
render() {
// Step 1: use the context prop
return <div>Filter is {this.context.filter}</div>;
}
}
// Step 2: be sure to set the contextType property of the component class
TodoHeader.contextType = TodoContext;
If you're using the functional component syntax, you can access the context with the useContext() function (we are using the function passed down inside the context, in this case):
const TodoFooter = props => {
const context = useContext(TodoContext);
return (
<div>
<button onClick={context.clear()}>Clear Completed</button>
</div>
);
};
