

Writing Our Own In-house Redux (Kind Of)
source link: https://engblog.yext.com/post/writing-our-own-redux
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Writing Our Own In-house Redux (Kind Of)
At Yext, we use React for all new frontend applications. When it comes to state management libraries to go with React, it doesn’t get much better than Redux. It allows you to define any number of reducers which comprise a global store, to dispatch actions to that store, and to subscribe to only the parts of state that you care about in each component.
On the other hand, for apps with relatively simple state, a few useState
calls should do the trick. However, while working on a large frontend project recently, I noticed that our state grew complex enough to warrant something more than some hooks. No team at Yext has used Redux thus far, and even despite its relatively small size (163 kB), we thought it overkill for our use case. What we really wanted was a basic Redux implementation without having to use a third-party library.
So, like any Yexter would, we set out to make our own Redux! Albeit with a simpler set of features, we still created a store-like state with a way to dispatch actions to that store. Here’s how:
The Reducer
First, we defined a custom reducer in its own file. A reducer is simply a function that accepts a current state and an action, and based on that action, returns a new state. The Redux convention is that every action is an object which has, at the very least, a type
property which is a string. In this post, we will demonstrate with a simple counter which we can increment, decrement, or set. Here’s what a simple reducer for the count state looks like:
// countReducer.js
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const SET_COUNT = 'SET_COUNT';
export default function countReducer(state, action) {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
case SET_COUNT:
return action.count;
default:
throw new Error(`Action type ${action.type} not recognized`);
}
}
Later, we’ll feed this function into a useReducer
hook which will automatically take care of updating the state for us when we dispatch actions.
The Store
I use the term “store” lightly here, because in this light Redux implementation, the store is nothing more than state stored in a top-level component. However, it still behaves much like a store in that it serves as the single source of truth for the application (or subset of the application). Additionally, we can dispatch actions to this “store” from anywhere.
As mentioned previously, we create this “store” using the useReducer
hook, which will give us a state as well as a dispatch
function to update that state. The hook automatically takes care of updating the state whenever it receives an action.
// TopLevelComponent.jsx
import React, { useReducer } from 'react';
import countReducer from './countReducer';
export default function TopLevelComponent(props) {
const [count, dispatch] = useReducer(countReducer, 0);
return (
<p>The current count is {count}.</p>
);
}
The second argument to useReducer
is the initial value of the state, which we will set to 0.
Now that we have created our “store”, we need to provide child components with a way to update that store in a convenient way.
Providing dispatch
To make our implementation even sleeker, we wanted to provide a way for child components to access the dispatch
function without manually passing it to them. To accomplish this, we used React Context. First, we created a context in the reducer file:
// countReducer.js
import { createContext } from 'react';
export const CountContext = createContext(null);
Next, we imported that context into our top-level reducer in order to provide the dispatch
function to all children:
// TopLevelComponent.jsx
import React, { useReducer } from 'react';
import countReducer, { CountContext } from './countReducer';
export default function TopLevelComponent(props) {
const [count, dispatch] = useReducer(countReducer, 0);
return (
<CountContext.Provider value={dispatch}>
{/* children */}
</CountContext.Provider>
);
}
Then, any child which requires the dispatch
function can grab it via the useContext
hook.
We didn’t make a way to provide the value of the state globally (as Redux usually does), but that could easily be accomplished by using another piece of context and its corresponding provider.
Action Creators
Manually constructing action objects in each component becomes tedious. For example, every time you wanted to set the count from a child component, you would have to do something like this:
// SomeChildComponent.jsx
dispatch({
type: SET_COUNT,
count: 5
});
Well, it’s not that tedious, but as the state grows increasingly complex, action creators become even more useful. We defined some action creators in our reducer file that other child components can import. These action creators take care of constructing the object for you given certain parameters. Here’s an example for the set count action:
// countReducer.js
export const setCount(count) {
return {
type: SET_COUNT,
count
};
}
Bring it All Together
Now, we can use the dispatch
function to update the global state from any child component:
// ChildComponent.jsx
import React, { useContext, useState } from 'react';
import { CountContext, setCount } from '../reducer';
export default function ChildComponent(props) {
const dispatch = useContext(Context);
const [value, setValue] = useState(0);
return (
<div>
<p>Enter a number:</p>
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
<button type="button" onClick={() => dispatch(setCount(value))}>
Set Count
</button>
</div>
);
}
Now, when a user clicks the “Set Count” button, the global count
state will be updated. Furthermore, any child which subscribes to that global state will automatically see all dispatched updates!
Just remember: using Redux is cool, but making your own (basic) Redux is far cooler 😎.
Recommend
-
46
I was inspired to write this today after a colleague new to React complained about how much boilerplate is involved when using Redux (and Redux-Saga). So I put this together for a couple reasons: We mistaken...
-
12
-
5
everybody's a CPU designer these days — Microsoft may be developing its own, in-house ARM CPU designs Bloomberg's unconfirmed report relies on confidential sources within Microsoft....
-
5
Let’s Create Our Own CryptocurrencyI’ve been itching to build my own cryptocurrency… and I shall give it an unoriginal & narcissistic name: Cranky Coin. After giving it a lot of thought, I decided to use Python. GIL thread concur...
-
16
How we ended up writing our own programming language Oct 21, 2014 lokad
-
10
Ruby Magic Brewing our own Template Lexer in Ruby Benedikt Deicke on Jul 2, 2019 “I absolutely love AppSignal.” Discover AppS...
-
9
Scratching our own itch The team behind AppSignal has been active in the Ruby on Rails community since 2005. We were the second company in the Netherlands to use it in production. We started AppSignal in 2012, after the launch of Rai...
-
4
BackRedux Who? Handle your own state insteadDecember 14th, 2021 · 3 min read
-
5
< BlogWriting Redux Reducers in Rust
-
11
React, ProseMirror, and ReduxBuild Your Own: React, ProseMirror, and ReduxThis course was originally designed as a two-week learning sprint to be taken on by the Oak team at the New York Times. The go...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK