The previous exercise had you set up a wrapper component around a Context .Provider
component. Now we’re going to make use of that wrapper component by working with React state.
Many React applications use prop drilling to pass down two values: a piece of state and the state updater function to update that state. Child components may then use the state updater function to change the state of their ancestors. For example:
const CounterApp = () => { const [count, setCount] = useState(0); return ( <Counter count={count} setCount={setCount} /> ); };
In this example, Counter
can use the setCount()
function to update the count
value of CounterApp
.
React Contexts can also be used to provide state and state updater functions. One common pattern is to have the context provide an object containing both of those values. Child components that consume the context can then use both (or either of) the state and the state updater function.
In this example, CounterArea
provides the count
value and the setCount()
function to its descendants using context. The Counter
component extracts both values from the provided context.
const CounterContext = React.createContext(); const CounterArea = ({ children }) => { const [count, setCount] = useState(0); return ( <CounterContext.Provider value={{ count, setCount }}> {children} </CounterContext.Provider> ); }; const Counter = () => { const { count, setCount } = useContext(CounterContext); return ( <button onClick={() => setCount(count => count+1)}> {count} </button> ); }; const CounterApp = () => { return ( <CounterArea> <Counter /> </CounterArea> ) }
In this exercise, you’ll add logic to the ThemeArea
component that sets up a piece of state and a state updater function for its context. In doing so, you’ll allow the ThemeContext
to have its theme updated by child components.
Instructions
Many React Contexts are made to store an object with multiple properties, rather than a single string value the way ThemeContext
does now. For this application, we’d like to share a state value and a state setter function to all consumers of ThemeContext
.
First, in the ThemeArea
component, call useState()
to create a piece of state named theme
along with a state setter function. Use the initialTheme
prop as the initial state value.
Now that we have multiple values to pass to consumers of ThemeContext
, we need to modify the value of the ThemeContext.Provider
component’svalue
prop.
- Provide an object containing both the
theme
andsetTheme
values to theThemeContext.Provider
component with thevalue
prop. - Then, in
ContactItem.js
, use destructuring to access only thetheme
value from the object returned byuseContext()
.
We only need
theme
for this component. We’ll usesetTheme
soon!
Great, now all children of ThemeArea
have access to both the theme
state and its setTheme
updater function. Let’s make a component that renders a button users can click to switch the theme between "dark"
and "light"
.
Create a new file in the editor named ThemeSwitcher.js
. Then:
- Create and export a
ThemeSwitcher
component in that file. ThemeSwitcher
should retrieve bothsetTheme
andtheme
fromThemeContext
as variables.- It should also render a button whose text content is
Theme is currently: {theme}
.
So far the ThemeSwitcher
component only displays the current theme. Let’s add it to the application to make sure it does that correctly before we add more features.
Import the ThemeSwitcher
component into ContactsSection.js
. Render it as a child of the ContactsSection
component, immediately after the h2.
At this point there should be buttons on the screen that display what theme their contacts section is rendering with. The buttons don’t do anything when clicked – yet!
In the ThemeSwitcher
component, add an onClick
callback on the <button>
. It should call setTheme
with "dark"
if the current theme is "light"
, or "light"
if the current theme is "dark"
.