The State of

State Management

Thupi Ceylons

Software Engineer at Danske Bank

But why?

Why am i doing this talk...

Living life as you do...

"There's a new state management system!" - colleague

...Migrating all my hobby projects

So i would like to

Share my learnings with you

So lets get the basics out of the way

What is state?

const Component = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      {count}
      <button onClick={() => setCount(count => ++count)}>+</button>
      <button onClick={() => setCount(count => --count)}>-</button>
    </div>
  );
}

React Component State

https://jsfiddle.net/3qLrcg2e/

const StateContext = React.createContext();

const Root = () => {
  const state = React.useState(0);
  return (
    <StateContext.Provider value={state}>
    	<Component />
    </StateContext.Provider>
  );
}

const Component = () => {
  const [count, setCount] = React.useContext(StateContext);
  return (
    <div>
      {count}
      <button onClick={() => setCount(count => ++count)}>+</button>
      <button onClick={() => setCount(count => --count)}>-</button>
    </div>
  );
}

ReactDOM.render(<Root />, document.querySelector("#app"))

React Context

https://jsfiddle.net/3qLrcg2e/1/

The mental model of React

view = f(state)

Okay thats cool...

Why do we need anything else?

State Management

Performance

Flexibility

Features

DX

State Management

Performance

Flexibility

Features

DX

Transient updates

Immutability/Reactivity

Selectors

Performance

Flexibility

Features

DX

Internal/external

Composable

derived or computed state

Performance

Flexibility

Features

DX

Runtime type checking

Timetravel

Patches

Performance

Flexibility

Features

DX

Debugging tools

Infered typechecking

Boilerplate

State Management

Performance

Flexibility

Features

DX

Lets take a look at a few options

A few state managements systems

Redux

Is based on Immutability

 

Is managed internally

 

Opinionated structure

 

Centralized state

Mobx

Is based on observability

 

Is managed externally

 

Flexible structure

 

Distributed state

Redux

Mobx

// Actions
export const add = () => ({
  type: 'ADD',
})
export const subtract = () => ({
  type: 'SUBTRACT',
})

// Reducers
const reducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'ADD':
      return { ...state, count: state.count + 1 }
    case 'SUBTRACT':
      return { ...state, count: state.count - 1 }
    default:
      return state
  }
}

// Selectors
const getCount = (state) => state.count;

const Component = ({count, add, subtract}) => {
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={add}>
        +
      </button>
      <button onClick={subtract}>
        -
      </button>
    </div>
  )
)

const mapStateToProps = state => ({
  count: getCount(state)
})

const mapDispatchToProps = dispatch => ({
  add: () => dispatch(actions.add()),
  subtract: () => dispatch(actions.subtract())
})

const ConnectedComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(Component)

// const store = createStore(reducer);
// render(
//  <Provider store={store}>
//    <App />
//  </Provider>,
//  document.getElementById('root')
// )

const appState = observable({
  count: 0,
  subtract: action(function() {
    appState.count -= 1;
  }),
  add: action(function () {
    appState.count += 1;
  })
})

const Component = observer(() => {
  return (
    <div>
      <h2>{appState.count}</h2>
      <button onClick={appState.add}>
        +
      </button>
      <button onClick={appState.subtract}>
        -
      </button>
    </div>
  )
})

But these are the OG state managers...

What about the new kids in the block

Jotai

Is based on Immutability

 

Is managed internally

 

Opinionated structure

 

Centralized state

Zustand

Is based on Immutability

 

Is managed externally

 

Flexible structure

 

Distributed state

Jotai

Zustand

const countAtom = atom(0);

const addCountAtom = atom(null, (_get, set) => {
  set(countAtom, (c) => c + 1);
});
const subtractCountAtom = atom(null, (_get, set) => {
  set(countAtom, (c) => c - 1);
});

const Component = () => {
  const [count] = useAtom(countAtom);
  const [, add] = useAtom(addCountAtom);
  const [, subtract] = useAtom(subtractCountAtom);
  
  return (
    <div>
      {count}
      <button onClick={add}>
        +
      </button>
      <button onClick={subtract}>
        -
      </button>
    </div>
  );
};
const useCountStore = create((set) => ({
  count: 0,
  add: () => set((prev) => ({ count: prev.count + 1 })),
  subtract: () => set((prev) => ({ count: prev.count - 1 }))
}));

const Component = () => {
  const state = useStore();
  return (
    <div>
      {state.count}
      <button onClick={state.add}>
        +
      </button>
      <button onClick={state.subtract}>
        -
      </button>
    </div>
  );
};

But which state manager did i pick?

Well, lets try to compare both

Zustand

Is based on Immutability

 

Is managed externally

 

Flexible structure

 

Distributed state

Valtio

Is based on Reactivity

 

Is managed externally

 

Flexible structure

 

Distributed state

(current)

(new)

const useCountStore = create((set) => ({
  count: 0,
  add: () => set((prev) => ({ count: prev.count + 1 })),
  subtract: () => set((prev) => ({ count: prev.count - 1 }))
}));

const Component = () => {
  const state = useStore();
  return (
    <div>
      {state.count}
      <button onClick={state.add}>
        +
      </button>
      <button onClick={state.subtract}>
        -
      </button>
    </div>
  );
};
const countState = proxy({
  count: 0,
});

const add = () => { countState.count += 1 };
const subtract = () => { countState.count -= 1 }; 

const Component = () => {
  const state = useSnapshot(countState);
  return (
    <div>
      {state.count}
      <button onClick={add}>
        +
      </button>
      <button onClick={subtract}>
        -
      </button>
    </div>
  );
};

Zustand

Valtio

(current)

(new)

Zustand

Immutability stays close to react

 

Works as expected.

 

Manual render optimization

 

Tends to use less memory

 

Less compatible with built-in react features

Valtio

Proxies are unusual in react.

 

Works magically

 

Automatic render optimization

 

Tends to use more memory

 

More compatible with built-in react features

(current)

(new)

Thanks for great state managers

Daishi Kato @ dai-shi

If someone comes from redux or likes react's natural immutable updates, zustand would fit well.
If someone is from vue/svelte/mobx, or new to JS and/or react, valtio's mutable model would fit well.

 

zustand is a very thin library and never behaves unexpectedly. valtio comes with proxy magic.

 

Oh my...

Did we cover it all?

Immer

Recoil

State Machines

Server cache state

Form state

unstated

Server side rendering support

Server Cache State

Packages: React-Query, Apollo

Purpose: Maintain server state

 

Finite State Machines

Packages: Xstate

Purpose: Maintain the relations between states.

Specialised
State Managements
Solutions

Useful links for when you are figuring out...

How to pick a state manager

  • All in one guide:
    • Video: https://www.youtube.com/watch?v=u_o09PD_qAs
    • Article: https://leerob.io/blog/react-state-management
       
  • Does it support concurrent rendering?
    • https://github.com/dai-shi/will-this-react-global-state-work-in-concurrent-rendering
       
  • Basic comparison between state managers
    • https://github.com/dai-shi/lets-compare-global-state-with-react-hooks

The State of State Management

By thupi

The State of State Management

  • 171