Updating my React Sudoku Solver app: Replacing Flux with Redux (part 1)

A while back I built a React frontend for a AWS Lambda Sudoku solver. At the time I used Flux (original source for this app is here), but I’m taking a look at updating it to replace Flux with Redux.

As a starting point I took another example React app, a simplest case app using Flux and then converted it to Redux. After working through the changes to update this app to use Redux, I put together a quick cheatsheet of changes here. Here’s both projects for a comparison:

https://github.com/kevinhooke/SimpleReactFluxExample

https://github.com/kevinhooke/SimpleReactReduxExample

Here’s a walkthrough of the main changes to an existing app (summarizing the steps from my cheatsheet, link above, and looking at some of the steps in more detail):

  • install react-redux and react-devtools-extension
  • create a store using Redux createStore, replacing previous Flux store that used Dispatcher, EmitEvent and [add|remove]ChangeListeners
  • create reducers to manipulate the Store
  • wrap root component with <Provider>
  • connect components with the Store

The Redux approach to maintaining state in the Store is significantly different from Flux:

  • Redux has a single store, whereas Flux can have many
  • State maintained in a Redux Store is manipulated with reducer functions
  • Reducer functions take the state to be manipulated and actions that are applied to the state to change it
  • Flux maintains a copy of the state in variables in the Store itself, whereas the current state of the store is passed as an argument to reducers, and a new updated copy is returned as the result

Since there’s a number of changes to the store, it’s worth looking at each of these in more detail.

Updating Flux Store to a Redux Store

Previously my Flux store maintained state in two vars:

var puzzleData = {}
var message = {}

These are no longer needed in the Redux store, but there in an initialState for initializing the Store. This can simply be the same as puzzleData before but just renamed for clarity:

const initialPuzzleData = {}

Next, each of the case statements in the Action can move over to the reducer function, and updated to manipulate the content of the store passed as a param and then return the modified store as the result. For example, previously each action was handled like this:

case 'NEW_DATA' :
  this.setData(action.data);
  this.emit('change');
  break;

With Redux, each case block is now going to look more like this:

case 'NEW_DATA' :
  return Object.assign( {}, action.data );

Create ActionCreators

In Flux, an Action combines a couple of concepts, it’s the payload to be dispatched to the Store as well as the mechanism for performing the dispatch of the Action. With Redux these concepts are separate, and the Action is merely the payload to apply to updating the Store. There is a concept of Action Creators though, that can be called to build the Action payload. Before with Flux we would have an Action function like this:

    initSamplePuzzle(){
        console.log("SudokuSolverAction initSamplePuzzle()");
        var puzzle = {
            rows:
                [
                    ["", "", "", "8", "1", "", "6", "7", ""],
                    ["", "", "7", "4", "9", "", "2", "", "8"],
                    ["", "6", "", "", "5", "", "1", "", "4"],
                    ["1", "", "", "", "", "3", "9", "", ""],
                    ["4", "", "", "", "8", "", "", "", "7"],
                    ["", "", "6", "9", "", "", "", "", "3"],
                    ["9", "", "2", "", "3", "", "", "6", ""],
                    ["6", "", "1", "", "7", "4", "3", "", ""],
                    ["", "3", "4", "", "6", "9", "", "", ""]
                ]
        }

        AppDispatcher.dispatch({
            actionName: 'NEW_DATA',
            data: puzzle.rows
        });
    }

and now this is simplified to build the payload, the Flux Dispatcher is removed, and the function. now just returns the payload to be processed:

    return {
        actionName: 'NEW_DATA',
        data: puzzle.rows
    };

Another simple example that builds an Action object for an update to the Store:

export function updateGrid(payload) {
    return { type: NEW_DATA, payload }
};

Connecting Components to the Store

In each Component, implement mapStateToProps and mapDispatchToProps to pass store and dispatch() as props to each Component, and call connect() to connect each Component with your Redux Store.

Here’s my matchStateToProps:

const mapStateToProps = state => {
    return { 
        grid: state.grid,
        message: state.message
     };
};

And here’s my mapDispatchToProps:

function mapDispatchToProps(dispatch) {
    return {
        updatePuzzleData :  grid => dispatch(updatePuzzleData(grid)),
        clearData : () => dispatch(clearData()),
        initSamplePuzzle : () => dispatch(initSamplePuzzle())
    }
}

mapDispatchToProps is where your Action Creators are called to create Action objects for modifying state, and then dispatched to apply against the state of the Store by your reducers.

Connect your components to your Store with a call to connect() :

const SudokuSolver = connect(mapStateToProps, mapDispatchToProps)(ConnectedSudokuSolver);

export default SudokuSolver;

Part 2

In part 2 I’ll look at moving the api call from the Flux Action to use Redux middleware and thunks.

WordPress 5.6 image upload error: not a valid JSON response

For larger image file uploads I’ve been getting this error recently:

Reducing the image file size and then retrying avoids the issue, but this adds additional steps.

Multiple posts online like this one suggest this is an error with the permalinks and resaving the current settings on that page in the Admin panel will fix the error, but this didn’t fix it for me.

Looking around in my server logs, I found this error in my /var/log/nginx/error.log:

2020/12/23 00:36:45 [error] 36#36: *311 client intended to send too large body: 4132609 bytes

This post suggests adding a client_max_body_size param in nginx.conf (or increasing the value already there). I added a larger value than the size in the error and this fixed the issue.