Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

The Basics

Okay, we're going to warm up by implementing a reducer in order to get everything up and running again.

We're going to implement the basic functionality.

const reducer = (state = [], action) => {
  return state;
};

And then we swap out that useState with a useReducer.

const [grudges, dispatch] = useReducer(reducer, []);

We're going to create an action type and an action creator.

const ADD_GRUDGE = 'ADD_GRUDGE';
const FORGIVE_GRUDGE = 'FORGIVE_GRUDGE';
const addGrudge = useCallback(
  ({ person, reason }) => {
    dispatch({
      type: ADD_GRUDGE,
      payload: {
        person,
        reason
      }
    });
  },
  [dispatch]
);

We'll add it to the reducer.

const reducer = (state = [], action) => {
  if (action.type === ADD_GRUDGE) {
    return [
      {
        id: uniqueId(),
        ...action.payload
      },
      ...state
    ];
  }
  return state;
};

Alright, pass it to the AddGrudge.

<AddGrudge onSubmit={addGrudge} />

And then we'll add it to the AddGrudge component.

const handleSubmit = useCallback(
  event => {
    event.preventDefault();
    onSubmit({ person, reason });
    setPerson('');
    setReason('');
  },
  [person, reason, onSubmit]
);

Forgiveness

Let's make an action creator

const forgiveGrudge = useCallback(
  id => {
    dispatch({
      type: FORGIVE_GRUDGE,
      payload: {
        id
      }
    });
  },
  [dispatch]
);

We'll also update the reducer here.

if (action.type === FORGIVE_GRUDGE) {
  return state.filter(grudge => grudge.id !== action.payload.id);
}

We'll thread through forgiveGrudge as onForgive.

<button onClick={() => onForgive(grudge.id)}>Forgive</button>

That prop drilling isn't great, but we'll deal with it in a bit.

Implementing Undo and Redo

We need to think about the past, present, and future.

const defaultState = {
  past: [],
  present: [],
  future: []
};

We've broken almost everything. So, let's make this a bit better.

const reducer = (state, action) => {
  if (action.type === ADD_GRUDGE) {
    return {
      past: [],
      present: [
        {
          id: uniqueId(),
          ...action.payload
        },
        ...state.present
      ],
      future: []
    };
  }

  if (action.type === FORGIVE_GRUDGE) {
    return {
      past: [],
      present: state.present.filter(grudge => grudge.id !== action.payload.id),
      future: []
    };
  }

  return state;
};

Adding to the Stack

past: [state.present, ...state.past]
if (action.type === UNDO) {
  const [newPresent, ...newPast] = state.past;
  return {
    past: newPast,
    present: newPresent,
    future: [state.present, ...state.present]
  };
}
const undo = useCallback(() => {
  dispatch({ type: UNDO });
}, [dispatch]);
<button disabled={!state.past.length} onClick={undo}>
  Undo
</button>

Getting Redo

if (action.type === REDO) {
  const [newPresent, ...newFuture] = state.future;
  return {
    past: [state.present, ...state.past],
    present: newPresent,
    future: newFuture
  };
}

Abstracting All of This

const useUndoReducer = (reducer, initialState) => {
  const undoState = {
    past: [],
    present: initialState,
    future: []
  };

  const undoReducer = (state, action) => {
    const newPresent = reducer(state, action);

    if (action.type === UNDO) {
      const [newPresent, ...newPast] = state.past;
      return {
        past: newPast,
        present: newPresent,
        future: [state.present, ...state.future]
      };
    }

    if (action.type === REDO) {
      const [newPresent, ...newFuture] = state.future;
      return {
        past: [state.present, ...state.past],
        present: newPresent,
        future: newFuture
      };
    }

    return {
      past: [state.present, ...state.past],
      present: newPresent,
      future: []
    };
  };

  return useReducer(undoReducer, undoState);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment