aweary / react-copy-write

✍️ Immutable state with a mutable API
MIT License
1.79k stars 54 forks source link

async mutators #19

Closed jamiebuilds closed 6 years ago

jamiebuilds commented 6 years ago

If createMutator passed the mutate() method instead of the draft it could better support async

const { createMutator } = createState({
  value: "",
  loading: false,
  error: null,
});

const updateValue = createMutator(async (mutate, value) => {
  mutate(draft => { draft.loading = true; });
  try {
    await fetch('/api/v1/value', { method: 'POST', ... });
    mutate(draft => { draft.value = value; });
  } catch (error) {
    mutate(draft => { draft.error = error; });
  } finally {
    mutate(draft => { draft.loading = false; });
  }
});
aweary commented 6 years ago

I was thinking that mutate could just be returned from createState as well. It's actually currently exported as update because I changed the name and forgot to remove it (😬). Do you think renaming it mutate and documenting it be sufficient?

I wrote createMutator because I liked the API of defining a mutator that has the draft automatically applied, but that's about the only reason. Exposing mutate directly should be safe.

jamiebuilds commented 6 years ago

I've been looking at ways to write states w/ their mutators in the same module, that might actually be the best way

states/counter.js

import createState from 'react-copy-write';

export type State = {
  count: number
};

export const initialState: State = {
  count: 0
};

// Export Provider, Consumer, wish it wasn't awkward to _not_ export `mutate` (update)
export const { Provider, Consumer, mutate } = createState(initialState);

// mutators:
export function increment() {
  mutate(state => { state.count + 1 });
}

export function decrement() {
  mutate(state => { state.count - 1 });
}

// selectors?
export function selectCount(state) {
  return state.count;
}

components/Counter.js

import { Consumer, increment, decrement, selectCount } from '../states/counter';

export default function Counter(props: {}) {
  return (
    <Consumer selector={selectCount}>
      {count => (
        <>
          <span>{count}</span>
          <button onClick={decrement}>-</button>
          <button onClick={increment}>+</button>
        </>
      )}
    </Consumer>
  );
}
aweary commented 6 years ago

Cool, I'll rename the update export and make a release when I'm back from vacation

aweary commented 6 years ago

mutate is returned from createState now

jamiebuilds commented 6 years ago

Do you want to document something like the above pattern?