Do you like Redux? Then you’ll love react-connect-context-hooks!
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:
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.cssconst 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.