Skip to content

flow-tools-dev/flow-state

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flow State

The smallest React global state management library I could write with the features I wanted.

Heavily inspired by the simplicity of Galactic State and the convenient feature set of Zustand, RTK, and Recoil.

Features

  • Simple state container built around React hooks.
  • Supports slice-based state out of the box.
  • Supports shallow merge of object-based Flow State out of the box.
  • Supports updating Flow State from anywhere in your app through the update function.
  • Subscribe to changes to Flow State anywhere in your app via the subscribe function.
  • Relies on useSyncExternalStore for consistency.
  • Dependency-free other than React itself.
  • Looks and feels like useState/useSelector/Zustand.
  • Written in Typescript.

Installation

npm install @flow-tools-dev/flow-state

Basic Usage

All you need to get started is to pass your initial state into createFlowStore.

// from any file... let's call this one userStore.ts
import { createFlowStore } from '@flow-tools/flow-state';

// the createFlowStore function returns everything you need.
export const store = createFlowStore({
  user: { name: 'Jimmy Bananas', age: 34 },
  description: 'A swell dude.',
});


// Then somewhere in a component far, far away...
import { useFlowState: useUserState } from '../userStore';

const [userProfile, setUserProfile] = useUserState();
// ^^ these work like normal react hooks.

The library is extremely basic, and easily extendable. So you can get super fancy in your store files.

import { createFlowStore } from '@flow-tools/flow-state';

const userStore = createFlowStore({
  user: { name: 'Jimmy Bananas', age: 34 },
  description: 'A good dude, that Jimmy.',
});

const logValOnChange = (value) => {
  console.log('VALUE!', value);
};
const unsubscribeLogger = userStore.subscribe(logValOnChange);

// make your own custom hooks as needed.
const useUserDescription = () =>
  userStore.useFlowState((state) => state.description);
// ^^ pass in selector functions to pull out specific pieces of your store.

const { useFlowState: useUserState, update: updateUser } = userStore;

export const { useUserState, useUserDescription, updateUser };
export default userStore;

Grab the full state, and go buck-wild.

import { useUserState } from '../userStore';

const MyComponent = () => {
  // in your component
  const [userProfile, setUserProfile] = useUserState();

  // will preserve the description state from above via shallow merge.
  setUserProfile({ user: { name: 'Jimmy B', age: 34 } }); // shallow merge.
  setUserProfile({ user: { name: 'Jimmy Bananas', age: 32 } }); // shallow merge.
  setUserProfile({ meh: {} }, true); // full state replacement. Removes description, user, etc. Just have meh now.

  return <></>
}

Grab a slice of state and also go buck-wild. For more details, see the API docs below.

import { useUserDescription } from '../userStore';

const MyComponent = () => {
  // Note that the setter is still the full update function
  const [userDescription, setUserProfile] = useUserDescription();

  // You can also return partial state, to be shallow merged.
  const checkIfMillennial = () => {
    setUserProfile((state) => {
      if (state.user.age > 30 && !state.isMillenial) {
        return { isMillennial: true };
      }
      return state;
    });
  };

  // Now after checking, isMillennial will be merged at the root level of your state!
  return <></>
};

And in a file, a million miles away, that isn't a react component -

import userStore from '../userStore';

updateStore.subscribe(logDataOrSomething);

const randomUpdateFunction = async (params) => {
  const data = await fetchData(params);
  if (data.error)
    userStore.update((state) => {
      /* do some stuff to update your state, and your components. */
    });
};

It's that easy. You want less? Take less. You want more? Take it all. You want middleware options? Ad-hoc subscriptions? Custom functionality written for each piece of state you declare? Have it. It's a small, easily extendable library. So do whatever you want with it.

API

createFlowStore(init)

Creates a new store. Returns:

  • useFlowState(): [T, update] - React hook to get access the Flow State and update function. Takes an optional selector function similar to RTK, or returns the entire state if no selector provided. Note - if the slice of state doesn't change identity, the component doesn't re-render.
  • update(partialOrFn, replace = false) - Updates Flow State. If the new state changed, your components update, and every listener is invoked with the new state.
    • If partialOrFn is a function, the full Flow State is passed in and the result is shallow merged with the current Flow State.
    • If partialOrFn is an object, it is shallow merged with the current Flow State.
    • If the partialOrFn is not an object, the Flow State is directly replaced.
    • If replace is true, then the Flow State is directly replaced.
  • getState - Returns the current Flow State.
  • subscribe - Subscribes a function to Flow State changes. Returns an unsubscribe function.
  • resetState - Resets the Flow State to the original value.
  • debug - Logs the current state of the store, and returns out an object that contains your state, your listeners, and your listener count.

Additional Notes

  • The shallow merge functionality works just like Zustand, and only on objects. All other data types will be simply replaced.
  • This follows the normal rules for React. If what is returns out of your selector function changes it's place in memory, your component will always re render. If it doesn't, it never will. For example -
const [derived, setFullState] = useFlowState(prev => ({
  id: prev.id,
  name: prev.name,
}))
// ^^ Unstable object identity. Recreated on every render. Good luck. This bad.

const [fullThing, setFullThing] = useFlowState();
const derived = { id: fullThing.id, name: fullThing.name };
// ^^ This better. Now React won't freak out.

Leave a star. Thank you so much for trying it out.

About

A minimal global React state library

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors