After learning about ReasonReact a few weeks ago I made a small sliding puzzle game to test the new syntax. One of the features I loved the most was the reducerComponent feature. So I decided to implement it in React with JavaScript.

In this blog post, I will speak about it and what I've learned new.

The project

I implemented the same game as with ReasonReact: a small sliding game puzzle.

Sliding Puzzle

I created the project with create react app.
In order to start implementing fast, I decided to use recompose.

You can find the source code here: ArmandDu/slidingPuzzleReact.
The ReasonReact version here: ArmandDu/slidingPuzzleReasonReact.
You can find the live demo here: https://puzzle.apptize.fr?v=react

WithReducerComponent

I've decided to make a Higher Order Function to implement my ReducerComponent.

To use it we only need a few things:

  • define the initialState
  • define the actionTypes
  • define a reducerFactory that will receive the actionTypes and return a reducer component.
  • Connect the HoC with your component.

Here is the signature of the WithReducerComponent function:

/**
** actionTypes: Array(string)
** reducerCreator: (actionTypes: Object) => (state: Object, action: {type, payload}) => state: Object
** initialState: () => Object | Object
** Component: Function | React node
*/

withReducerComponent(actionTypes, reducerCreator, initialState)(Component);

I decided to pass a reducerCreator instead of passing the reducer function directly because I was too lazy to have to case write string literals twice. I'm converting the actionType Array to an object of {[actionType]: actionType}. Usage below.

The HoC accepts a list of actionTypes emulating the actions type variant in Reason the reducerCreator and the initialState. It will then inject some new props to the component:

  • a store that I could have named state: the component's state
  • an actionTypes object, the same passed in the reducerCreator
  • a send method that accepts an actionType and an optional payload.

This is an example definition:

const initialState = {
    selectedPuzzle: null,
    difficulty: "medium"
};

const actions = ["SELECT_DIFFICULTY", "SELECT_PUZZLE"];

const reducerCreator = ({SELECT_DIFFICULTY, SELECT_PUZZLE}) => {

    return (state, {type, payload}) => {
        switch (type) {
            case SELECT_DIFFICULTY:
                return {...state, difficulty: payload};
            case SELECT_PUZZLE:
                return {...state, selectedPuzzle: payload};
            default:
                return state;
        }

    };
};

const reduceComponent = withReducerComponent(actions, reducerCreator, initialState);

export default reduceComponent(NewGame);

We create a simple componentReducer that will respond to "SELECT_DIFFICULTY" and "SELECT_PUZZLE" actions.

To use it in our component we do:

export class NewGame extends React.Component {

    render() {

        // props injected from the HoC
        const { send, actions, store: {selectedPuzzle, difficulty} } = this.props;

        return Map()
        .set(false, (
            <Menu 
                  selectDifficulty={d => send(actions.SELECT_DIFFICULTY, d}
                  selectPuzzle=={p => send(actions.SELECT_PUZZLE, p}
                  ... />
        ))
        .get(store.dififculty && store.selectedPuzzle, (
            <Play onQuit=={() => send(actions.SELECT_PUZZLE, null)} .../>
         ))
        
    }
}

Switch and Map

In this repo, I mixed a lot the usage of switch and immutable's Map mainly for the sake of testing both in different scenarios.

I'm using them as "replacements" for the Pattern Matching in Reason.

While reviewing the code I saw that I could be more consistent with using switch in the reducer function and using Map when I'm mapping data or when I'm conditionally rendering components.

Conclusion

Now I have a new pattern feature that I can use and like!

I find that managing the state change in one place and updating it by dispatching actions is much cleaner and simpler to maintain than having a dozen methods that will manage the state themselves.

In the future I'll probably improve it and maybe even make a package out of it.