Playing with React Hooks

Published on
5 min read
Related tags

This article was written the week following React Conf 2018, after the team announced their proposal for React Hooks.

This article was originally posted on Medium back in 2019, and I've ported it here for historical purposes/nostalgia 😄 Read on medium.com.

Last week at React Conf 2018 the team announced a new proposal for React Hooks, a new API that allows you to use state and other features more directly without the need for classes. There are no breaking changes and it's completely opt-in, so if you're not hooked just yet (heh) you don't have to use them and can continue using classes, no problems. Pretty exciting stuff!

It's currently only in the proposal stage but if you're like me you love playing around with new features, so you can start integrating them right now with the React v16.7.0-alpha.0 release.

One hook in particular has me pretty excited, and that is useReducer. It offers Redux-like functionality without needing to bring in a separate state management package 😮 Really cool!

With useReducer we get the current state along with a matching dispatch method. This is particularly useful with the new useContext hook, as I discovered while playing around with the two together over the weekend. I created a simple little app that allows you to update the width, height and background of a square.

Inside Context.js we initialize the context and create the reducer, and also create the provider component (AppContextProvider) which wraps around the Controls and Square components (they are siblings). This means when the context is updated via the Controls component, Square is able to get the updates without having to pass any props down. Pretty cool 😃

Let's go over how all this works. Starting in Context.js:

context.js
Copy

_42
import React from 'react';
_42
_42
// Create the context as AppContext
_42
const AppContext = React.createContext();
_42
_42
// Define some initial state
_42
const initialState = {
_42
width: 320,
_42
height: 250,
_42
activeColor: '#F44336',
_42
};
_42
_42
// Create the reducer
_42
const reducer = (state, action) => {
_42
switch (action.type) {
_42
case 'width':
_42
return { ...state, width: action.payload };
_42
case 'height':
_42
return { ...state, height: action.payload };
_42
case 'activeColor':
_42
return { ...state, activeColor: action.payload };
_42
default:
_42
return initialState;
_42
}
_42
};
_42
_42
const AppContextProvider = props => {
_42
// Create the provider as AppContextProvider
_42
// useReducer uses the above reducer and initial state, and returns
_42
// the current state and matching dispatch method
_42
const [state, dispatch] = React.useReducer(reducer, initialState);
_42
_42
// Return the Provider and pass through the state and dispatch as value
_42
return (
_42
<AppContext.Provider value={{ state, dispatch }}>
_42
{props.children}
_42
</AppContext.Provider>
_42
);
_42
};
_42
_42
// Make sure to export both context and provider
_42
export { AppContext, AppContextProvider };

You don't have to create the Provider in a separate component like above, but I like doing it that way because it keeps things very clean in App.js. Personal preference 🤷‍

The resulting App.js imports the Provider which then wraps around the components, enabling them to subscribe to the context changes.

app.js
Copy

_15
import { AppContextProvider } from 'path/to/Context';
_15
_15
// Import our other components
_15
import Controls from 'path/to/Controls';
_15
import Square from 'path/to/Square';
_15
_15
export default function App() {
_15
return (
_15
// Wrap the Provider around our components. Nice and clean!
_15
<AppContextProvider>
_15
<Controls />
_15
<Square />
_15
</AppContextProvider>
_15
);
_15
}

In the Controls component, we then have to import the context for use with useContext. From that, we can pull off the state and dispatch.

controls.js
Copy

_25
import React from 'react';
_25
import { AppContext } from 'path/to/context';
_25
_25
export default function Controls() {
_25
// Use AppContext as the context and pull off state and dispatch
_25
const { state, dispatch } = React.useContext(AppContext);
_25
_25
// Create the functions to update state, using the dispatch method passed in
_25
// from the provider
_25
const setWidth = value => dispatch({ type: 'width', payload: value });
_25
const setHeight = value => dispatch({ type: 'height', payload: value });
_25
const setActiveColor = color =>
_25
dispatch({ type: 'activeColor', payload: color });
_25
_25
return (
_25
...
_25
// Now we can update state using each of the above functions in the
_25
// onChange handler e.g. with the color picker that would look like:
_25
<CirclePicker
_25
color={state.activeColor}
_25
onChange={color => setActiveColor(color.hex)}
_25
/>
_25
...
_25
);
_25
}

And then all we have to do in Square component is pull the state off from the context and pass it down as props for styling (I'm using styled-components). Easy peasy!

square.js
Copy

_26
import { AppContext } from '../../context';
_26
_26
export default function Square() {
_26
// Use AppContext as the context and pull off state
_26
const { state } = useContext(AppContext);
_26
_26
// Pull off specific state from the context
_26
const { width, height, activeColor } = state;
_26
_26
return (
_26
// Pass state down as props
_26
<SquareStyle
_26
className="square"
_26
width={width}
_26
height={height}
_26
activeColor={activeColor}
_26
/>
_26
);
_26
}
_26
_26
// And use within the styled-component
_26
const SquareStyle = styled.div`
_26
background: ${props => props.activeColor};
_26
height: ${props => props.height + 'px'};
_26
width: ${props => props.width + 'px'};
_26
`;

After some inevitable initial confusion, I feel like ultimately hooks are pretty intuitive to me. I'm excited to see where things go and welcome the new addition to React. Will definitely be using them down the line when they are officially released.


Thanks for reading, and if you want to dive deeper into the above, the code is available on Github 👍