For anyone who wants to skip the article and see the end result, I’ve taken what I’ve written here and made a library out if it using hooks: . It has zero dependencies (other than react as a peer dependency) and at just 3kb, it’s pretty lightweight. use-simple-state In recent years the scope of web applications has increased dramatically and as the requirements for our apps grow, so does the complexity. In order to make this added complexity easier to deal with, certain techniques and patterns are increasingly being used to make developer’s lives easier and to help us build more robust applications. One of the main areas where complexity has grown is in managing our application’s state, so to combat this developers are using libraries that provide abstractions for updating and accessing their app’s state. The most notable example being , which is an implementation of the . Redux Flux pattern Once a developer has learned how to use a library like Redux, they may still be left wondering just how exactly everything is working “under the hood” since it’s not obvious at first, even if the more general concept of updating a globally-available object is easy to grasp. In this article we’ll build our own state management solution for a React application, completely from scratch. We’ll start with a basic solution that can be implemented in just a few lines of code and gradually work in more advanced features until we have something resembling Redux. The Basic Idea Any state management tool needs only a couple of things: a global state value available to the entire application, as well as the ability to read and update it. That’s it, seriously. Just to show you how simple a state manager can be, here’s a barebones vanilla JavaScript implementation: The above example is as basic as it gets, yet it still ticks all the boxes: A globally-available value representing our app’s state: state Ability to read our state: getState Ability to update our state: setState The above example is too simple for most real-world applications so next we’re going to start implementing a workable solution for use in a React app. We’ll start by refactoring the previous example to make it work in React and build on from there. State Management in React In order to make a React-based version of our previous solution we’re going to need to leverage two React features. The first feature being plain old class components, a.k.a . stateful components The second feature is the , which is used to make data available to your entire React application. A context has two parts: a and a . The provider, as the name suggests, provides the (data) to an application. While a consumer is used when we want to access a context. context API provider consumer context A good way to understand context is this: if are used to pass data through your components, then is used to pass data. props explicitly context implicitly Building our State Manager Now we know the tools we want to use, it’s just a case of putting them together. All we’re going to do is create a context to hold our global state, then wrap that context’s in a and use that to manage the state. provider stateful component First off, let’s create our context using , this gives us our and : React.createContext Provider Consumer Next, we need to wrap our in a in order to leverage it to manage our app’s state. We also want to export the with a more specific name: Provider stateful component consumer In the above code sample, our is simply a component that accepts a prop as the initial state and makes whatever is contained in that prop available to any component underneath it in the component tree. If no is provided then an empty object is used instead. StateProvider state state Using our is as simple as wrapping it around our application’s root component: StateProvider Now we’ve done that, we can access our state from anywhere inside using a consumer. In this case we’ve also initialised our state to be an object with a single property: , so whenever we access our state now, that is what we will find. MyApp count Consumers use to pass the context data, this can be seen below where a function is a child of . The parameter passed to that function represents our application’s current state, so as per our , will be equal to . render props StateConsumer state initialState state.count 0 An important thing to note about our is that it automatically subscribes to changes in the context, so when our state changes the component will re-render in order to display the updates. This is just the default behaviour for consumers, we haven’t done anything to enable it. StateConsumer Updating State So far we’ve built something that allows us to read our state, as well as automatically update when it changes. Now we need a way to update the state of our app, to do this we’re simply going to update the state in our . StateProvider As you may have noticed earlier, we passed a prop called to our , which was then passed to the property. This is what we will be updating, using React’s built-in method: state StateProvider component’s state this.setState Continuing the theme of keeping it simple, we’ve just passed to our context. This meant we had to change the value of our context slightly; instead of only passing we’re now passing an object with two properties: and . this.setState this.state state setState Whenever we use our we’ll use a to get and , so now we can read from and write to our state object: StateConsumer destructuring assignment state setState Something to note is that since we’ve simply passed React’s built-in method as our function, additional properties will be merged with the existing state. This means that if we had a second property in addition to then it would be preserved automatically. this.setState setState count Now we’ve built something that work in the real world (albeit not very efficiently). It’s got a simple API that should feel familiar to React developers, plus it leverages built-in tools so we haven’t added any new dependencies either. If state management libraries felt a bit ‘magical’ before, hopefully we’ve already been able to shed some light on what the internals of one might look like. could Bells and Whistles Those of you already familiar with Redux may have noticed that our solution is lacking in a few areas: It has no built-in way of handling side effects, functionality you’d get via . Redux middleware Complex state updates would be messy when written inline with our function and we’re relying on React’s default behaviour to handle our state update logic, there’s also no built-in way of reusing state updates, something you get from . setState this.setState Redux reducers We’ve also got no way of handling , which is usually provided by libraries like and . asynchronous actions Redux Thunk Redux Saga Crucially, we have no way for our consumers to subscribe to of the state, meaning that when any part of our state updates every consumer will re-render. part To overcome this, we’re going to emulate Redux by implementing our own actions, reducers, and middleware. We’ll also add built-in support for async actions. After that, we’re going to implement a way for our consumers to only listen for changes in a subset of our state. Finally we’ll also look at how we can refactor our code so we’re using the brand new . Hooks API A Brief Introduction to Redux Disclaimer: the following is only meant to give you enough of an understanding to continue with the article, I’d highly recommend reading the for a more thorough explanation. official introduction to Redux If you already have a good understanding of Redux, feel free to skip this bit. Below is a simplified diagram of the data flow in a Redux application: Redux data flow As you can see, there is a data flow — we an action from which our reducers derive an updated state, no data is traveling back and forth between different parts of our application. one way dispatch In a bit more detail: First, we dispatch an action which a change to our state, e.g. to increase a number by 1. Contrast this to our previous, more imperative method whereby we essentially manipulated the state directly: . describes dispatch({ type: INCREMENT_BY_ONE }) count setState({ count: count + 1 }) The action then passes through our Redux middlewares are optional functions that can perform side effects as a result of actions, e.g. if a action is dispatched you may use a middleware function to remove all user data from local storage before passing the action along to your reducer. If you’re familiar with middleware in , this is a very similar concept. middleware. SIGN_OUT Express Finally, our actions arrive at our reducers which take the action, as well as any accompanying data, and use that plus the existing state to derive a new state. Let’s say we an action called and we also send an accompanying value (called a ) which is the amount we wish to add to our state. Our reducer will check for an action, when it finds one it will take the as well as the current value in our state and add the two together to produce our updated state. dispatch ADD payload ADD payload The function signature for a reducer is as follows: (state, action) => nextState A reducer should simply be a function of and The API is simple yet powerful. A key thing to note is that reducers should always be , so that they are always deterministic. state action. pure functions Actions + Dispatch Now that we’ve briefly gone over some of the key parts of a Redux app, we need to modify our app to emulate the same behaviour. First things first: we need some actions and a way to dispatch them. For our actions we’re going to use action creators, these are simply functions that create actions. Action creators make testing, reusing, and passing payloads to our actions much easier. We’re also going to create some action types, these are just string constants, since they’ll be re-used in our reducers we’ll store them in variables: For now, we’re going to implement a placeholder function. Our placeholder will just be an empty function, which we’ll use to replace the function in our context. We’ll come back to this in a moment, since we don’t yet have any reducers to dispatch our actions to. dispatch setState Reducers Now we’ve got actions we just need some reducers to send them to. Thinking back to the reducer function signature, it’s simply a pure function of actions and state: (state, action) => nextState Knowing this, all we need to do is pass our component’s state and the dispatched action into our reducers. For the reducers, we simply want an array of functions that adhere to the above signature. We use an array so that we can simply iterate over it using until we arrive at our new state: Array.reduce As you can see, all we do to get our new state is to compute it using our reducers, then just like before we simply call to update ‘s component state. this.setState StateProvider Now we just need an actual reducer: Our reducer just checks the incoming and if it finds a match it’ll update the state accordingly, otherwise we just fall through the statement and return an value from our function by default. An important difference between Redux’s reducers and our own is that when we don’t want to update the state, usually because we didn’t find a matching action type, we return a value, whereas with Redux you would return the unchanged state. action.type switch undefined falsy And pass our reducer to our : StateProvider Now we can finally dispatch some actions and watch our state update according to which ones we send: Middleware Now we’ve got something that resembles Redux a fair bit, we just need a way to handle side effects. To achieve this we’re going to allow our user to pass middleware functions that will be called whenever an action is dispatched. We also want our middleware functions to be able to bail us out of state updates, so if is returned from one we won’t pass the action to our reducer. Redux handles this a little differently — in Redux middleware you need to manually pass the action along to the next middleware, if it is not passed along using Redux’s function the action will not reach the reducer and the state will not update. null next Now let’s write a simple middleware. We want it to look for an action, if it finds one it should print the sum of the and the existing state, but block the actual state update. ADD_N payload count Just like our reducers, we’ll pass any middlewares to our in an array: StateProvider Finally we need to call all of our middleware and use the result to determine whether or not we want to abort an update. Since we’ve just passed an array and we’re looking for a single value, we’re going to use to get our result. Just like with our reducers we’ll iterate through the array while calling each function, then pass the result to a variable that we’ll name . Array.reduce continueUpdate Since middleware is considered an we don’t want it to be mandatory, so if no prop is found in our we’ll make equal by default. We’ll also add a array as the default prop, just so doesn’t throw an error if nothing is passed. advanced feature middleware StateProvider continueUpdate undefined middleware middleware.reduce As you can see on line 13, we check to see what our middleware functions return. If a value is encountered we will skip the rest of the middleware functions and the value of will be , meaning we will abort the update. null continueUpdate null Asynchronous Actions Since we want our state manager to be useful in the real world we’re going to add support for async actions, which will mean we can handle common tasks like network requests with ease. We’re going to borrow from a bit here since the API is simple, intuitive, and powerful. Redux Thunk All we’re going to do is check to see if an uncalled function was passed to dispatch, if we find one we’ll call it while passing and which gives the user everything they need to write async actions. Take this authentication action as an example: dispatch state In the above example we have an action creator called , instead of returning an object however, it returns a function that accepts . This allows the user to dispatch synchronous actions before and after an asynchronous API call. Depending on the result of the API call a different action will be dispatched, in this case we send an error action if something goes wrong. logIn dispatch Implementing this is as easy as checking for a type in the method in our : action function _dispatch StateProvider Two things to note here: where we call as a function we pass so the user can access the existing state inside the async action, we’re also returning the result of the function call, allowing developers to get a return value from their async actions which opens up more possibilities, such as chaining promises from . action this.state dispatch Avoiding Unnecessary Re-renders Something that often gets overlooked yet is an essential feature of Redux (or more accurately, — the React binding for Redux) is it’s ability to only re-render a component when necessary. To achieve this it uses the , which takes a mapping function — — and will only trigger a re-render of the component it’s attached to when the output of (just from now on) changes. If this were not the case then every component that uses to subscribe to store changes would be re-rendered . React-Redux connect higher order component mapStateToProps mapStateToProps mapState connect every single time the state updates Thinking about what we need to do, we’re going to need a way to store previous outputs of so we can compare it to any new results to decide if we want to go ahead and re-render our component. To do this we’re going to use a process called . Like many things in our industry it’s a big word for a fairly simple process, especially for us since we can leverage to store the subset of our state in and only update it when we detect changes in the output of . mapState memoization React.Component this.state mapState Next we’re going to need a way to skip unnecessary component updates. React provides an easy way for us to do this by using the lifecycle method . It takes any incoming props and state as parameters which allows us to compare the values to our existing props and state, if we return the update will go ahead but if we return React will skip rendering. shouldComponentUpdate true false The above is an outline for what we’re going to do next. It has all the main pieces in place: it receives updates from our context, it implements and , and it also takes a render prop as a child — just like the default consumer. We also initialise our consumer’s initial state by using the passed function. getDerivedStateFromProps shouldComponentUpdate mapState As it is right now though, will only render once when it receives the first state update. After that it will log the incoming and existing state and return , blocking any updates. shouldComponentUpdate false The above solution also calls inside and as we know always triggers a re-render. Since we’re also returning from , this will cause an additional re-render, so to get around this we’re going to derive our state using the lifecycle , then we’ll use to determine whether we want to carry on with the rendering process based on our derived state. this.setState shouldComponentUpdate this.setState true shouldComponentUpdate getDerivedStateFromProps shouldComponentUpdate If we inspect our console we can see that the global state updates, while our component blocks any updates to it’s object and therefore skips rendering: this.state Three attempts to update state, but thisState only changes once So now that we know how to prevent an unnecessary update we need a way to intelligently determine when our consumer should render. If we wanted to we could over an incoming state object and check every single property to see if it’s changed, but while this would be a good exercise to improve our understanding it could be bad for performance. We can’t know how deep or complex any incoming state object might be and a recursive function will happily carry on indefinitely if the exit condition is never met, so we’re going to limit the scope of our comparison. recurse Just like Redux, we’re going to implement a compare function. “Shallow” here refers to the depth of the properties at which we’re going to see if our objects are equal, meaning we’re only going to check 1 level deep. So we’re going to check if each property at the top level of our new state is equal to a property of the same name on our existing state, if properties of the same name don’t exist or they have different values, we’ll proceed with rendering, otherwise we assume our states are the same and we abort the render. shallow First we start off with a simple check that will look at whether both states are objects, if not then we skip rendering. After this initial check we convert our current state into an array of key/value pairs and check the values of each property against that of our incoming state object by reducing the array into a single boolean. That’s the hard part out of way. Now that we want to use our function it’s essentially just a case of calling it and checking the result. If it returns we’re going to return to allow the re-render, otherwise we simply return to skip the update (and our derived state is discarded). We also want to apply our function, if it exists. shallowCompare true true false mapDispatch Lastly we need to pass a function to our consumer that only maps part of our state, so we’ll pass it as a prop to our updated : mapState StateConsumer And now we’re only subscribed to changes in , so if we update our component will ignore the changes in our global state and avoid a re-render. greeting count Quick Recap If you’ve made it this far you’ll have seen how to implement a Redux-like state management library, complete with reducers and actions. We’ve also covered more advanced topics, such as asynchronous actions, middleware, and how to make it so we only receive the state updates we want to avoid re-rendering our consumers each time the global state updates. While Redux has a lot more going on under the hood than our solution, hopefully this has helped clear up some of the core concepts and shown that while Redux is generally considered to be more of an advanced topic, it’s implementation is relatively simple. For a more thorough understanding of Redux’s internals, I’d highly recommend reading the . source code on Github The solution we have so far has all the tools and attributes necessary to be used in a real-world project now. We could start using this in a React project and we wouldn’t need Redux unless we wanted to access some of the really advanced features. Hooks If you haven’t yet heard, hooks are quickly becoming the next big thing in React. Here’s a brief explanation from the official introduction: Hooks are a new feature proposal that lets you use state and other React features without writing a class. Hooks give us all the power of higher order components and render props with a cleaner and more intuitive API. Let’s take a look at how they work using a quick example showing the basic hook : useState In the above example we initialise a new state by passing to which returns our state: , as well as an updater function: . If you’ve not seen this before you may wonder how doesn’t get reinitialised to on every render — it’s because React handles this internally, so we don’t need to worry about that. 0 useState count setCount useState 0 So let’s forget about middleware and async actions for a moment and re-implement our provider using the hook, which works just like , except actions are dispatched to a reducer from which the new state is derived, just like what we’re building. useReducer useState Knowing this, we simply copy our reducer logic from our old into our new, functional : StateProvider StateProvider That’s how simple it can be, but while we want to keep things simple, we still aren’t fully harnessing the power of hooks just yet. We can also use hooks to swap our for our own custom hook, which we’ll do by wrapping the hook: StateConsumer useContext Whereas before we were destructuring and when we created our context, this time we store it in a single variable which we pass to in order for us to access our context without a . We’ve also named our custom hook , since is a default hook. Provider Consumer useContext Consumer useStore useState Next we simply refactor the way in which we consume our context: Hopefully these examples have gone some way in showing how intuitive, simple, and powerful hooks are. We’ve reduced the amount of code needed and given ourselves a nice, simple API to work with. We also want to get our middleware and built-in support for asynchronous actions working again. To do this we’re going to wrap our inside a custom hook, one to be used specially in our , and then simply re-use the previous logic from our old stateful component. useReducer StateProvider As with our old solution, we want middleware to be optional, so we add an empty array as a default again — although this time we use a default parameter instead of default props. Similar to our old dispatch function, we call our middleware and, if we carry on with the state update. We’ve also made no changes to how we handle async actions. continueUpdate !== null Finally, we pass the result of and it’s parameters to our provider, which has shrunk considerably: useStateProvider And that’s it! 🎉 However… One thing you may have noticed is that our hooks implementation has no way to skip unnecessary updates. This is because of how hooks are called in the body of a function component — at that stage React has no way of bailing out of the rendering process (not without some hacks). There’s no need to worry though, and plan to provide a way for us to abort an update from functional components. the React team are aware of this Once we’ve got an official way to bail out of rendering inside a function component I’ll come back here and update this blog post. In the meantime, the library I’ve written out of the hooks implementation comes with a consumer so we can access this functionality. In Summary To summarise, we’ve taken a look at the most barebones state manager possible and incrementally built upon it until we ended up with something resembling Redux — complete with actions, reducers, middleware, and a way to diff state updates to improve performance. We’ve also looked at how we can simplify our code using the brand new hooks API. Hopefully you’ve found something useful in this article and I was able to shed a bit of light on some more advanced concepts while showing that a lot of the tools we use may be more simple than they first appear. As briefly mentioned at the beginning, I’ve written a library, , off the back of this article. You can see it on on my , where I’ve used hooks for the , which includes a couple of additional features. Use Simple State Github page final implementation Twitter: @josh_jahans