donavon / use-persisted-state

A custom React Hook that provides a multi-instance, multi-tab/browser shared and persistent state.
MIT License
1.38k stars 96 forks source link

Add reducer equivalent of the API #3

Open AndrewIngram opened 5 years ago

AndrewIngram commented 5 years ago

This looks great, at first glance the main thing lacking is an equivalent of useReducer for more complex state management. Any interest in extending it a bit?

donavon commented 5 years ago

Hmm... Interesting. Have any ideas? Seems like a different package, but would share many of the same functionality.

hazelweakly commented 5 years ago

useState is actually implemented in react using useReducer. The default reducer in this case is just (state, action) => typeof action === 'function' ? action(state) : action;

So it seems like it should be quite sufficient to add a parameter to usePersistedState and implement a createPersistedReducer (to keep a simple API and account for the fact that you fallback to the default state hooks).

It gets tricky, though, because usePersistedState sets the state on storage change events and when another instance emits a setState event. In contrast, when using useReducer, dispatch never explicitly gets the state, so we can't really use useReducer.

That being said, there's a neat package react-enhanced-reducer-hook that augments useReducer in a clever way--by using setState and implementing dispatch itself. Looks like this library could use a similar strategy to implement a useReducer variant.

A small preliminary sketch:

const createPersistedReducer = (
  key,
  provider = global.localStorage,
  reducer = (state, action) => typeof action === 'function' ? action(state) : action,
) => {
  if (provider) {
    const storage = createStorage(provider);
    return initialState => usePersistedReducer(reducer, initialState, key, storage);
  }
  return useReducer;
};

with usePersistedReducer being:

// Omitting code that stays the same
const usePersistedReducer = (reducer, initialState, key, { get, set }) => {
  // ...same ...
  const dispatch = action => {
    const next = reducer(state, action);
    setState(next);
    return action;
  };

  // ... same ...

  return [state, dispatch];
};

Alternatively, depending on how much you want to condense down the code changes, I can see all of this extra functionality being added in with ~5 lines of changed code by adding a conditional or two to the existing functions.

donavon commented 5 years ago

@jared-w

Sorry it's taken been so long to respond. I've got a lot of pokers in the fire at once.

This is a great plan. I'll try to get this implemented as soon as I can. I'm going something similar in a different project.