Redux is a popular library used to manage global application state, at least the claim. Lots of developers struggle to understand what Redux does and how to use it properly. When saying global state most developers would intrinsically imagine a simple library lifting up individual states into a shared global object. This isn’t Redux. Although Redux is a small library (2kB, including dependencies), the documentation and the complimentary libraries required makes it comprehensive. For experienced and inaugurated developers redux.js.org it’s a well-written and detailed guide that covers every aspect of Redux, for others it’s opaque and overly complex. An complexity that is also increasing present in React and which the upcoming Svelte framework highlights.

This complexity isn’t hidden for anybody. The large text sections under each of the menu items (Getting started, Tutorial,FAQ and Best Practices) and the nested menu items overwhelms any newcomer. Tellingly, navigating to Getting started sends you to the “official recommended approach for writing Redux logic”, another library that recommended to learn and which likewise contains plenty documentation. That is part of the problem, even after learning the Redux concepts (explained into detail and illustrated) like store setup, reducers, and actions, you can’t really use Redux.

Presumably the most common use case fetching data via an async function isn’t possible. For that you need another third party library, Redux Thunk for instance, which unlike Redux itself has easily readable documentation. Redux Thunk will add action creators on top of the actions provided by Redux, once you have added it to your array middleware when setting up global store with the rootReducers. When you finally think you have it figured out, you might want to try another library for handling async actions (Redux Saga) that changes the way Redux works slightly or alternative Redux architecture that changes it comprehensively.

One alternative

Fortunately there are alternatives to this. One of the simplest and most elegant solutions is Pullstate. Pullstate essentially let’s your application access global state via a useState hooks. Pullstate isn’t passed through the top component, like Redux’s Provider component, but manages state separately and used directly through hooks in the components. This is as easy as it gets. Global state is merely a file containing your initial state:

import { Store } from 'pullstate';

// So simple, this is how React's context API should have been all along…
const UIStore = new Store({ isDarkMode: false });

export default UIStore;

Usage in a component:

import React from 'react';
import UIStore from './store';

function App() {
  const isDarkMode = UIStore.useState((state) => state.isDarkMode);
  return <p>Is dark mode: {isDarkMode ? 'yes' : 'no'}</p>;
}

Updating state done via UIStore.update function:

<button onClick={() => UIStore.update((state) => !state.isDarkMode)}>Toggle Dark Mode</button>

Async action

As said above, a common usage is running an async function call, for instance fetching user data. Pullstate adds a few convinient functions which gives additonal formalised tools for cache, error and success handling:

import { createAsyncAction, errorResult, successResult } from 'pullstate';

const whoAmI = createAsyncAction(async () => {
  const user = await fetchAPI('/whoami');

  if (user.error) {
    return errorResult(['NO_USER_FOUND'], 'User information not found.');
  }

  return successResult(user); // We could also append this to `UIStore` if we wanted to have a way to access it throught there
});

export default whoAmI;

Usage in a component:

import whoAmI from './whoAmI';

function App() {
  // `useBeckon` calls actual fetch function and returns usuful states for tracking the async action
  // `useWatch` does the same, except it doesn't actual call the async function, only watches for states changes
  const [finished, result, updating] = whoAmI.useBeckon();

  if (!finished) {
    return <p>Loading…</p>;
  }

  if (result.error) {
    return <p>Something went wrong!</p>;
  }

  return <p>You are logged in as {result.user.name}</p>;
}

Pullstate is easy to use and understand and requires a lot less code than an implementation in Redux. The investment required to learn Redux, doesn’t pay off and there are plenty of competing state management libraries.