Typescript, React, Redux, Thunk, and Material-ui template -- now with less boilerplate!
Tags: webdev, showdev, react, typescript
Published: 2020-03-16 18:13:24

**tldr** *: I've forked the official Redux Typescript create react app template, switched it to use functional components and added [Material-UI](https://material-ui.com/) support. Source code is [here](https://github.com/jacobbaker/cra-template-redux-typescript-mui), follow me on [Twitter](https://twitter.com/thatonejakeb) for updates and nonsense, stay safe and [wash your hands](https://www.youtube.com/watch?v=dDHJW4r3elE&vl=en) ❤️*

---

Last week I wrote about the updated version of my Typescript, React, Redux, Thunk, and Material-UI skeleton app.

Well, as is the case with most tech *stuff*, it has been superseded by a new and improved version!

In the comments of the previous post I was kindly pointed towards the official [Redux+Typescript CRA template](https://github.com/reduxjs/cra-template-redux-typescript) by a maintainer which, amongst other things, uses Redux Toolkit to reduce the amount of boilerplate needed.

Just as a brief example of just how much is cut out, in the original skeleton you have the following breakdown of files for each feature:

- actions.ts
- types.ts
- reducer.ts
- thunk.ts

Whereas using Redux Toolkit you end up with:

- slice.ts

Equally nice is there is no more need for `connect` or `mapStateToProps` and the confusion they bring.

So, for a concrete example, here's how I had the counter implemented without Redux Toolkit:

#### types.ts

```javascript
export interface ExampleState {
    isFetching: boolean;
    count: number;
    error?: {
        message: string
    }
}

export const FETCH_EXAMPLE_REQUEST = "example/FETCH_EXAMPLE_REQUEST";
export const FETCH_EXAMPLE_SUCCESS = "example/FETCH_EXAMPLE_SUCCESS";
export const FETCH_EXAMPLE_FAILURE = "example/FETCH_EXAMPLE_FAILURE";

interface FetchExampleRequestAction {
    type: typeof FETCH_EXAMPLE_REQUEST,
    payload: {
        isFetching: boolean
        error: {
            message: string
        }
    }
}

interface FetchExampleSuccessAction {
    type: typeof FETCH_EXAMPLE_SUCCESS,
    payload: {
        isFetching: boolean
        count: number
    }
}

interface FetchExampleFailureAction {
    type: typeof FETCH_EXAMPLE_FAILURE,
    payload: {
        isFetching: boolean,
        error: {
            message: string
        }
    }
}

export type ExampleActionTypes = FetchExampleRequestAction | FetchExampleSuccessAction | FetchExampleFailureAction;
```

#### action.ts 

```javascript
import { FETCH_EXAMPLE_REQUEST, FETCH_EXAMPLE_SUCCESS, FETCH_EXAMPLE_FAILURE } from "./types";

export function fetchExampleRequest() {
    return {
        type: FETCH_EXAMPLE_REQUEST,
        payload: {
            isFetching: true,
            error: undefined
        }
    }
}

export function fetchExampleSuccess(count: number) {
    return {
        type: FETCH_EXAMPLE_SUCCESS,
        payload: {
            isFetching: false,
            count
        }
    }
}

export function fetchExampleFailure(message: string) {
    return {
        type: FETCH_EXAMPLE_FAILURE,
        payload: {
            isFetching: false,
            error: {
                message
            }
        }
    }
}
```

#### reducer.ts

```javascript
import {
    ExampleState,
    FETCH_EXAMPLE_REQUEST,
    FETCH_EXAMPLE_SUCCESS,
    FETCH_EXAMPLE_FAILURE,
    ExampleActionTypes
} from "./types";

const initialState: ExampleState = {
    isFetching: false,
    count: 0,
    error: undefined
};

export function exampleReducer(
    state = initialState,
    action: ExampleActionTypes
): ExampleState {
    switch(action.type) {
        case FETCH_EXAMPLE_REQUEST:
            return {
                ...state,
                isFetching: action.payload.isFetching,
                error: action.payload.error
            };
        case FETCH_EXAMPLE_SUCCESS:
            return {
                ...state,
                isFetching: action.payload.isFetching,
                count: action.payload.count
            }
        case FETCH_EXAMPLE_FAILURE:
            return {
                ...state,
                isFetching: action.payload.isFetching,
                error: action.payload.error,
            }
        default:
            return state;
    }
}
```

####thunk.ts

```javascript
import { Action } from "redux";
import { ThunkAction } from "redux-thunk";
import {
  fetchExampleRequest,
  fetchExampleSuccess,
  fetchExampleFailure
} from "./actions";
import { AppState } from "../";

export const fetchExample = (
  count: number
): ThunkAction<void, AppState, null, Action<string>> => async dispatch => {
  dispatch(fetchExampleRequest());

  setTimeout(() => {
    var randomErrorNum = Math.floor(Math.random() * count) + 1;

    if (randomErrorNum === count) {
      dispatch(fetchExampleFailure("Unable to increment count."));
    } else {
      dispatch(fetchExampleSuccess(count + 10));
    }
  }, 1000);
};

```

Which I might add is an incredibly small, novel, feature and that level of boilerplate is needed for each feature added. It can get quite irritating.

Now on to the new implementation that takes advantage of [Redux Toolkit](https://reactjs.org/docs/hooks-state.html). 

####slice.ts

```javascript
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AppThunk, RootState } from '../../app/store';

interface CounterState {
  value: number;
}

const initialState: CounterState = {
  value: 0,
};

export const slice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: state => {
      state.value += 10;
    },
  },
});

export const { increment, incrementAsync } = slice.actions;

export const incrementAsync = (): AppThunk => dispatch => {
  setTimeout(() => {
    dispatch(increment());
  }, 1000);
};

export const selectCount = (state: RootState) => state.counter.value;

export default slice.reducer;

```

And that's it for definitions! 

You would be right in asking where the `isFetching` part went. In this example I've replaced it with a [State Hook](https://reactjs.org/docs/hooks-state.html) in the component:

```javascript
const [isWorking, setIsWorking] = useState(false);

// [.. snip...]

const doAsyncIncrement = () => {
  setIsWorking(true);
  dispatch(incrementAsync(Number(incrementAmount || 0)));

  setTimeout(() => {
    setIsWorking(false);
  }, 1000);
};

// [... snip...]

<Button 
  className={classes.button}
  onClick={() =>
    doAsyncIncrement();
  }
>
Increment
</Button>
```

It such a nice way of working that I've forked the CRA Redux Template and ported my original skeleton app. I've added Material-UI support and switched it to use functional components.

You can find the source here:

Github: [https://github.com/jacobbaker/cra-template-redux-typescript-mui](https://github.com/jacobbaker/cra-template-redux-typescript-mui)

Or start a new project using it with:

```npx create-react-app my-app --template redux-typescript-mui```

Let me know if you've any comments or questions here or [@thatonejakeb](https://twitter.com/thatonejakeb).