Pullstate - Simple hooks-based state management for React

Max Lynch - Sep 21 '20 - - Dev Community

State management is one of the most important pieces of an app, and there are a ton of choices for those in the React ecosystem.

In particular, developers building iOS and Android mobile apps with React using Capacitor and Ionic React often ask us for state management recommendations. Of course, there's Redux, which I remain a major fan of, but also much simpler state management approaches like MobX and rolling your own using the Context API.

I've spent a lot of time using Redux and also the bespoke approach with the Context API. Yet, I wasn't satisfied. I wanted to find something that was simple but high performance, and had native integration with Hooks and Function components which I now use exclusively in React (sorry, never want to write the word class ever again 😆).

That's when I stumbled on Pullstate. Pullstate is a small, relatively unknown library (just 300 stars at the time of this writing), but I expect it will become much more popular in time.

Exploring Pullstate

Pullstate provides a simple Store object that is registered globally, and provides hooks for accessing data from that store in a component:

store.ts:

interface StoreType {
  user: User | null;
  currentProject: Project | null;
}

const MyStore = new Store<StoreType>({
 user: null,
 currentProject: null
});

export default MyStore;
Enter fullscreen mode Exit fullscreen mode

Then, in your component, simply use the useState method provided on the store to select data from the store:

const UserProfile: React.FC = () => {
  const user = MyStore.useState(s => s.user);
}
Enter fullscreen mode Exit fullscreen mode

Modifying state

To update state in the store, use the update method:

const setUser = (user: User) => {
  MyStore.update((s, o) => {
    s.user = user;
  });
}
Enter fullscreen mode Exit fullscreen mode

The update function works by mutating a Draft of the state. That draft is then processed to produce a new state.

Usually, a state mutation would raise a red flag, but the magic of Pullstate comes from a really interesting project called Immer. Immer essentially proxies an object and then turns mutations on that object into a new object (in my limited experience with it). Sort of how the vdom does diffing to figure out a new DOM tree.

This is incredibly powerful and simple, but does have a few gotcha's. First, reference comparisons on objects in the s value above will fail, because they are actually Proxy objects. That means doing something like this won't work:

MyStore.update(s => {
  s.project = s.projects.find(p => p === newProject)
});
Enter fullscreen mode Exit fullscreen mode

Instead, use the second argument, o above, which contains the un-proxied original state. Another gotcha is making sure not to return anything from the update function.

Next steps

After having used Pullstate, I will have a hard time not recommending it to all Ionic React developers, and those using Capacitor with other React UI libraries.

I think Pullstate is a great middle ground between being simple for small projects, but clearly capable of scaling to much more complicated projects. For larger projects multiple stores can be created in parallel, for a sort of redux reducer-inspired organization.

Pullstate also comes with some convenience helpers for async actions to cut down on async state boilerplate (such as handling success and failure states), though I have not used those extensively yet.

Next on my list is exploring how this might work with something like reselect for building reusable, memoized selectors.

What do you think? Have you used Pullstate? Please share in the comments!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .