Keep your application’s state organized with React Hooks.

Do you like Redux? Then you’ll love react-connect-context-hooks!

Adrian Gallardo
DailyJS
Published in
4 min readFeb 25, 2020

--

react-connect-context-hooks is a library inspired in react-redux and parts of its ecosystem (redux-thunk and reselect), but simpler and with a smaller footprint (only ~1.5kb gzipped and tree-shakeable).

It uses React hooks under the hood and provides strong and cohesive APIs to enable access to your application’s state.

It provides a lot of good stuff out-of-the-box:

  • Connect your dumb components with your application’s store with a HOC.
  • Select from the store only the data your components need.
  • Call sync or async actions with no extra pain.
  • Derive computed state and memoize their results.
  • Connect with one or multiple Contexts with little effort.
  • Separate your store in different feature-modules.

If you have experience with Redux you’ll notice a lot of similarities and this is on purpose. The main idea is not re-invent the wheel, but instead provide a lightweight library that enables you to do almost the same with lower bundle-sizes and fewer things to learn!

Let’s dive into code

Following instructions will be easy-peasy for experienced Redux developers. If you are not one of them just follow the instructions and you’ll be fine.

We’ll be working on creating a really simple Counter app. If you want to dive into a more advanced example I recommend you to take a look at the examples folder of the repository.

Here is the working example:

Simple Counter App using react-connect-context-hooks.

Step 0: Create a new App with create-react-app

You can start by creating a new app with create-react-app; this way your code structure will look similar to the used on the example.

Then, install react-connect-context-hooks by running:

npm install --save react-connect-context-hooks

Step 1: Create the Context Provider

Create the following file on: src/counter/store/CounterProvider.jsx

import createContextProvider, { connectContextFactory } from 'react-connect-context-hooks';import counterReducer, { initialState } from './counterReducer';
import counterActions from './counterActions';
const [CounterProvider, CounterContext] = createContextProvider(counterReducer, initialState, counterActions);const withCounter = connectContextFactory(CounterContext);export default CounterProvider;export {
withCounter,
};

This provider will allow the rest of your application to access the global state and actions stored in it.

Step 2: Create the counter actions

If you are familiar with Redux you’ll find these very similar to actionCreators in the way defined by redux-thunk(a function that receives dispatch and state and returns a function that triggers an action).

If you are not familiar with any of these concepts you can continue with the examples and dig on them later.

Create the following file on: src/counter/store/counterActions.js

const ACTIONS = {
INCREMENT: 'INCREMENT',
DECREMENT: 'DECREMENT',
};
const increment = (dispatch, state) => (amount) => {
dispatch({
type: ACTIONS.INCREMENT,
payload: { amount },
});
}
const decrement = (dispatch, state) => (amount) => {
dispatch({
type: ACTIONS.DECREMENT,
payload: { amount },
});
}
const actions = {
increment,
decrement,
}
export default actions;export {
ACTIONS,
};

Step 3: Create the counter reducer

Again, reducer concept will be familiar for Redux users. You can also check documentation on React useReducer hook.

Create the following file on: src/counter/store/counterReducer.js

import { ACTIONS } from './counterActions';const initialState = {
count: 0,
};
function reducer(state, action) {
const { amount } = action.payload;

switch (action.type) {
case ACTIONS.INCREMENT:
return { count: state.count + amount };

case ACTIONS.DECREMENT:
return { count: state.count - amount };
default:
return state;
}
}
export default reducer;export {
initialState,
};

Step 4: Create the Counter component

We are almost there… Now we’ll create the component that makes use of the current state and actions.

Create the following file on: src/counter/Counter.jsx

import React from 'react';import { withCounter } from './store/CounterProvider';const Counter = ({ count, increment, decrement }) => {
const [amount, setAmount] = React.useState(1);

const updateAmount = (event) => {
setAmount(parseInt(event.target.value, 10));
}
return (
<div>
<
h1>Counter Component</h1>
<p>
<b>Amount:</b>
<input type="number" value={amount} onChange={updateAmount} />
</p>
<p>
<b>Count: </b>
<span>{count}</span>
</p>
<hr />
<button onClick={() => decrement(amount)}>Decrement</button>
<button onClick={() => increment(amount)}>Increment</button>
</div>
);
}
// Export isolated component for unit-testing
export {
Counter,
}
// Export the connected component as default
export default withCounter(Counter, {
stateSelectors: ['count'],
actionSelectors: ['increment', 'decrement'],
});

Here we are using the withCounter HOC created before. This is a function that accepts the Component you want to connect with the store values and a configuration object. In this object, we are defining which values and actions we want to “select” from our store to provide to the component as props.

You can check the README file for additional information on how to use this configuration object.

Step 5: Wrap your App with Counter Provider

Finally, modify src/App.jsx to include Counter component and wrap your App content with CounterProvider.

import React from 'react';import CounterProvider from './counter/store/CounterProvider';
import Counter from './counter/Counter';
// Optional: you can import ./App.css for better styling
// You can copy get the file from: https://github.com/edriang/react-connect-context-hooks/blob/master/examples/counter/src/App.css
const App = () => (
<CounterProvider>
<div className="App">
<header className="App-header">
<Counter />
</header>
</div>
</CounterProvider>
);
export default App;

Step 6: Run your app

Yay!! We are ready to test our App! Just run npm start and test the app in your browser.

Conclusion

We’ve implemented a solid and scalable state-management solution following best practices and with little effort.

The best part of react-connect-context-hooks is it adds no extra dependencies as it uses React.hooks internally, but at the same time provides nice and complete features inspired on Redux ecosystem.

Next steps

I invite you to take a full look at the README file to get to know all the features of this library. Also, take a look at todomvc for a more complex example using two Providers and calling async REST actions.

If you have any questions or suggestions you can create a response here or issue a ticket on Github repository.

--

--