Category Archives: Redux

Using the Redux Toolkit

I’ve covered Redux on this blog in the past, but the “modern” approach is to use the Redux Toolkit which cleans up and simplifies some of the code. In some cases I found it more complicated, because it obviously does a lot and abstracts a lot.

Going over the basics

Let’s go over some of the basics of Redux first…

Redux is a global state management system. It’s made up of three key parts, the store which as the name suggests, is where your state is stored. The shape of the data within the store is defined by your needs (i.e. no prerequisites). The store is used for global state, so to partition the store for ease of working with your data, we create slices. We’ll generally have slices based upon the domain, so maybe a slice for feature flags, another for component state, another for an API and so on.

Next we have actions, these are (as the name suggests) used to perform actions against our data. In Redux we have an object with a type and some (optional) payload. The type is the name of the action,

{type: "INCREMENT", payload: 3 }

Finally we have reducers which we use, based upon the type of actions, to carry out updates of state. It’s important to remember that Redux data should be immutable, hence the reducer doesn’t change the state itself, but instead copies the state updating the new version of the state before passing this new copy back

RTK

Let’s now create a bare minimum Redux toolkit store

Note: I’m using TypeScript in my examples

import { configureStore } from "@reduxjs/toolkit"

export const store = configureStore({
  reducer: {}.
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

The code above just creates a Redux store, there’s no reducers. This store is not too useful at this point, but bare with me, let’s now quickly pivot to look at how we might use our store in a React app

import { Provider } from "react-redux"
import { store } from "./state/store.ts"

<Provider store={store}>
   <App />
</Provider>

By using the Provider at the App level our store is now available to our entire app.

As mentioned, the store above is pretty useless at the moment, let’s use the example from Redux/RTK to add a simple counter to the store. This represents one slice of the store hence, we’ll name the file counter.slice.ts (although RTK tends to go with counterSlice.ts style names).

First create a folder for your slices within the store folder, i.e. store/slices. Ofcourse you can place these where best suit, but this is the folder structure I’m using here.

Create the file counter.slice.ts inside store/slices. This, as you probably realised, will contain the counter slice within the state manager, i.e. the data specific to the counter

import { createSlice } from "@reduxjs/toolkit";

interface CounterState {
  value: number;
}

const intialState: CounterState = {
  value: 0;
};

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    }
  },
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;

In this example we appear to be mutating state, but behind the scenes createSlice handles copying our state (RTK uses Immer to handle such immutability), if you prefer you can use the more obvious state.value + 1.

This file includes the CounterState interface (obviously not required for pure JavaScript) which is a representation of what our counter state will look like, in this case we just store a number, the current counter value. We also have these actions within the reducer, increment and decrement.

We create an initialState so our store has any defaults or starting points, for example in this case we set count initially to zero.

Next, we need to export our actions using export const { increment, decrement } = counterSlice.actions and finally for this section, we now need to register the slice with the store, this is accomplished by adding it to the store reducer, like this

import counterReducer from "./counter/counterSlice";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  }
});

At this point, everything is hooked up in the store, so let’s use our increment/decrement actions. We use the useSelector to get our current state and useDispatch to dispatch the actions to the the store

import { useSelector } from "react-redux";
import { RootState } from "../state/store";
import { decrement, increment } from "../state/counter/counterSlice";

export const Counter = () => {
  const count = useSelector((states: RootState) => state.counter.value);
  const dispatch = useDispatch(); // dispatch actions

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>

}

Passing parameters

These examples are using simple actions. Let’s extend then by adding a incrementBy

Add a new action to counterSlice like this

incrementBy: (states, action: PayloadAction<number>) => {
  state.value += action.payload;
}

<em>Note: Whilst the += makes it look like we're mutating state, this is just a reminder, in this instance code within RTK is handling this as an immutable update.</em>

The PayloadAction only handles a single parameter, hence if we want to handle multiple parameters, we need to create an interface (in TypeScript) to define them. Let's move from the counter example and assume our Router is accepting multiple query parameters which we want to store in our store. We'll use an example of the github URL where by you can access my blog post code via <a href="https://github.com/putridparrot/blog-projects" rel="noopener" target="_blank">https://github.com/putridparrot/blog-projects</a>, so the first parameter might be gitUser, in this case <em>putridparrot</em>, followed by the repo name - hence we'd create our queryParams.slice.ts as follows

[code]
import { PayloadAction, createSlice } from "@reduxjs/toolkit";

export interface IQueryParams {
  gitUser?: string;
  repoName?: string;
}

const initialState: IQueryParams = {
  gitUser: "",
  repoName: "",
}

const queryParamsSlice = createSlice({
  name: "queryParams",
  initialState: initialState,
  reducers: {
    setQueryParams: (_, action: PayloadAction<IQueryParams>) => 
      action.payload,
  },
});

export const { setQueryParams } = queryParamsSlice.actions;

export default queryParamsSlice.reducer;

Everything’s pretty much as our counter example apart from the PayloadAction<IQueryParams>. In this example we’re actually just passing everything into the store so just returning the payload via setQueryParams.

Async actions

RTK supplies a way to incorporate async actions into the store. If we have something like a network call or another process we know may take some time, we’ll want to make it async. Just for a simple example, we’ll assume increment is now handled via either a network call or some longer running piece of code. We create an async thunk like this

export const incrementAsync = createAsyncThunk(
  "counter/incrementAsync",
  async (amount: number) => {
    // simulate a network call
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return amount;
  }
);

We’re supplying the name of our function counter/incrementAsync here. We now need to add this function to our slice, so after the reducers we add the following

extraReducers: (builder)  => {
  // we can accept state but for this example, we've no need
  builder.addCase(incrementAsync.pending, () => { 
    console.log("Pending");
  }).addCase(incrementAsync.fulfilled, (state, action: PayloadAction<number>) => {
    state.value += action.payload;
  });
}

Notice that each “case” is based upon some state which comes from createAsyncThunk. This is very cool as we now have the ability to update things based upon the current state of the async call, i.e. maybe we display a spinner for pending and remove when fulfilled or rejected occurs and in the case of rejected we now display an error toast message or the

Dispatch

To actually call into the store, i.e. call and action, we need to use code like the following

const dispatch = useDispatch<AppDispatch>();

Usually we’ll just wrap both the dispatch and selector into the following to make working with TypeScript simpler (just place this is some utility file such as hooks.ts or the classic helper.ts named file)

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

So now we can get the current count and update like this

import { decrement, increment } from '../store/slices/counter.slice'
import { useAppDispatch, useAppSelector } from '../store/hook'

export function Counter() {
  const count = useAppSelector((state) => state.counter.value)
  const dispatch = useAppDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

Calling services or API’s

The example within the RTK documentation is a call to the pokeapi, so let’s start with that. RTK documentation seems to suggest such API’s go into a folder named services, so we’ll stick with that although they are essentially just other slices of global state (at least in my view).

In our services folder add pokemin.ts and here’s the contents of the file

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const pokemonApi = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
  tagTypes: [],
  endpoints: (builder) => ({
    getPokemonByName: builder.query({
      query: (name: string) => `pokemon/${name}`,
    }),
  }),
})

// Export hooks for usage in functional components
export const { useGetPokemonByNameQuery } = pokemonApi

We used createAsyncThunk earlier and ofcourse we can easily implement services/network/API calls via the same mechanism, but the createApi method, which extends createAsyncThunk, generates React hooks for us. We also have options to handle caching and more from here.

In this example we use the fetchBaseQuery defining the baseUrl. This is a lightweight method wrapping the fetch method.

We define our endpoints, i.e. the API to our service and then at the end of the code we export the code generation hook, useGetPokemonByNameQuery.

To use this code within our store we need to go back to store.ts and add the following to the rootReducer

api: pokemonApi.reducer,

and to the middleware we need to concat the API like this

getDefaultMiddleware()
  .concat(pokemonApi.middleware),

Finally we’ll want to use this API, and we simply use the hook like this

export const Pokemon = ({ name, pollingInterval }: { name: string, pollingInterval: number }) => {
  const { data, error, isLoading, isFetching } = useGetPokemonByNameQuery(
    name,
    {
      pollingInterval,
    },
  );

Notice that we now get those async thunk states, but named differently. So we have the data returned, a potential error. We also have isLoading and isFetching for handling progress info.

Multiple parameters and multiple API’s

As mentioned previously RTK accepts a single object as input and this is the same for createApi actions, so if you have a situation where you need to pass multiple parameters to your API, then you handle in the same way (although in this example I’m creating via an anonymous type), i.e.

getContract: builder.query<any, {contractId?: string, version?: string}>({
  query: (arg) => {
    const { contractId, version } = arg;        
    return {
      url: `someurl?contractId=${contractId}&version=${version}`,
   }
  },

With regards multiple API’s, let’s say we have a pokemon and contracts api (I admit an odd combination). Then to register them with the store, as seen with a single API we concat them with the middleware, but we don’t appear to concat each, instead we concat an array like this

getDefaultMiddleware()
  .concat([contractsApi.middleware,  pokemonApi.middleware]),

Dynamic baseUrl and/or setting headers etc.

In the pokemon API example we had a fixed URL for the service, but when developing the front and back end we’ll often have dev and/or test and then production environments. In this case we’ll want to change the baseUrl based upon the environment. In such cases and also when we want to apply our own headers, for example let’s assume our API requires an SSO token, then we’ll want to include that in the call in some way or other.

Here’s our dynamicBaseQuery which we’re actually using the store to store something called enviroment which we’re assume contained data such as the the current environment URL along with the current SSO token for the user for the environment. In the second line we set the baseUrl and then it’s used in the fetchBaseQuery. We also use this to set up the headers.

const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (args, WebApi, extraOptions) => {
  const baseUrl = (WebApi.getState() as any).environment.appSettings.contractsApiUrl;
  const rawBaseQuery = fetchBaseQuery({
    baseUrl,
    prepareHeaders: async (headers, { getState }) => {
      const token = (getState() as any).environment.token;
      if (headers) {
        headers.set("Accept", "application/json");
        if(token) {
          headers.set("authorization", `Bearer ${token}`);
        }
      }

      return headers;
    },
  });
  return rawBaseQuery(args, WebApi, extraOptions);
};

To use this we simple replace the baseQuery value with dynamicBaseQuery, like this snippet

export const contractsApi = createApi({
  reducerPath: "contractsApi",
  baseQuery: dynamicBaseQuery,
  // rest of the code ommitted

Tooling

I highly recommend installing the Redux Dev Tools for your browser. These tools allow you to easily see the state of your Redux store (and as RTK is built on Redux we can see our RTK defined store) and best of all, RTK is automatically configured to work with the Redux Dev Tools, so now additional code is required.

Code

Code for this post is available in my blog-projects.

Note: Not all code is hooked up and being used, but hopefully there’s enough to show how things might look.

Redux observable

In the previous post we looked at one way to handle application side effects, such as asynchronous loading of data using Redux sagas.

There’s absolutely nothing wrong with redux saga’s but there’s a few alternatives. The alternative we’re going to look at is redux-observable.

So what’s the big difference between redux-sagas and redux-observables? I’ve not run any performance or effeciency testing against the two so I’m going to solely comment on their usages. Saga’s use generator functions whilst Epic’s (the term used within redux-observable as something analogous to Saga in redux-saga) uses, well you guessed it, Observables, i.e. rxjs.

Instead of yield put etc. in a Saga, we return a Observable.create and call next to pass data to the redux store. I’ve been asked “which should I choose?” by other developers and there really isn’t a clear reason to choose one over the other (if I get chance to try to check performance etc. I may amend this post).

I would say, if you’re already including rxjs in your application or you have a good understanding of rxjs, then redux-observables will probably be the best choice. If you’ve not really got an understanding of rxjs or don’t wish to bring in a dependency on rxjs, then stick to sagas.

I could (and probably will) write a post on using rxjs, but to summarise – rxjs (Reactive Extensions) came originally from .NET and offered a push style paradigm for development along with better concurrency capabilities and composability in a declarative manner. Whilst rxjs is not an exact copy (i.e. it uses a different way to compose observable data) it does offer similar capabilities. When abused, Observables can be hard to understand, but the powerful nature of the functionality/operators you get makes them far more powerful than saga’s – but then again if you have a good library of functions you can implement similar functionality to rxjs in sagas.

Okay, enough talk, let’s write code. I’m going to layout things just like the redux-saga post (and yes, even copy and paste some text) to give a sort of comparison of writing the two.

Assuming you have a React application created, we need run the following

  • yarn add react-redux
  • yarn add redux-observable

To create a simple demo, we’ll change the App.js file to

import React from 'react';
import store from './store';
import { Fetch } from './rootReducer';

function App() {

  function handleDoFetch() {
    store.dispatch({type: Fetch});
  }

  return (
    <div className="App">
      <button onClick={handleDoFetch}>Do Fetch</button>
    </div>
  );
}

export default App;

So this will simply dispatch an action which will ultimately be handled by our observable. Before this happens let’s create a redux store and set up the redux observable middleware, here’s my store.js file

import { createStore, applyMiddleware, combineReducers } from "redux";
import { createEpicMiddleware } from 'redux-observable';
import rootReducer from "./rootReducer";
import rootEpic from "./rootEpic";

export const epicMiddleware = createEpicMiddleware();

const store = applyMiddleware(epicMiddleware)(createStore)(
  combineReducers({
    rootReducer,
  })
);

epicMiddleware.run(rootEpic);

export default store;

We don’t need to combineReducers as there’s only one, but it’s there for an example of setting up multiple reducers. Let’s now create a very basic reducer named rootReducer.js

export const Fetch = "FETCH";
export const FetchEnded = "FETCH_ENDED";

export default (state = {}, action) => {
  switch (action.type) {
    case FetchEnded:
      console.log("Fetch Ended");
      return {
        ...state,
        data: "Fetch Ended"
      }
    default:
      break;
  }   
  return state;
}

Notice we’ve got two actions exported, Fetch and FetchEnded but there’s nothing handling Fetch in this case. This is because redux middleware will in essence pass this through to the redux-observable we’re about to create. We could also handle Fetch here and still handle it also within the epic, the point being the epic (via the observable and ofType) is going to handle this action when it see’s it.

Now we’ve got everything in place, let’s put the final piece in place, the epic will be stored in rootEpic.js and here it is

import { Fetch, FetchEnded } from "./rootReducer";
import { Observable } from "rxjs/internal/Observable";
import { mergeMap } from "rxjs/operators";
import { ofType } from "redux-observable";

function rootEpic(
    action$,
    _state$,
    _dependencies) {
  
    return action$.pipe(
      ofType(Fetch),
      mergeMap(action => { 
        return Observable.create(o => {
            console.log("fetchData")
            o.next({ type: FetchEnded });  
        })
    }));
}

export default rootEpic;

Notice that the rootEpic function returns an Observable via Observable.create and it uses next to inform any subscribers (in this case the middleware) to changes of state. Obviously this example is stupidly simple in that it just dispatches FetchEnded to the subscriber(s).

It might be the observable calls next many time for different values but in this example we’ve kept things simple. Running the application will display a button and using the browser’s dev tools, when the button is pressed the Fetch action is detected by the epic and the returned pipe, which itself uses the observable which then dispatches a FetchEnded action, which is handled by the reducer.

As stated, our example is very simple but in a real world scenario this function could be acting as a websocket client and for every value returned would placed into the next function until cancelled or maybe an error occurred.

Another thing to be aware of is that whilst the rootEpic pipe will be created once (in our case when added to the middleware for example) the pip is called for each action through redux and hence we must filter the actions we want to handle using ofType and even actions dispatched via the observable will come through this epic.

Redux and storybook

We’ve implemented our React UI and set-up storybook to test it but we’re using redux as our store, so how do we use this in storybook?

Storybook allow us to create a decorator which is really just a wrapper around our story, so for example we add a decorator using .addDecorator like this

storiesOf("SomeComponent", module)
  .addDecorator(withProvider)
  .add("default", () => 
    <SomeComponent />
 );

Within the .addDecorator we can add more React code or HTML, maybe to position our test component centrally in the screen or in this case we can use the same code to wrap a Provider.

As you can see from the above code we’ve got a withProvider value which looks like this

const withProvider = (story) => <Provider store={store}>{story()}</Provider>

Hence the code takes a story (the story we’re testing) and we simply wrap it within a Provider element having previously created the redux store using the standard createStore function, i.e.

const store = createStore(reducer);

Now we can test our UI/UX code within storybook using the redux store for it’s state.

Redux actions and reducers

We tend to split our redux code into two “sections”, an action function and a reducer function which usually uses a switch to then return state based upon the supplied action.

Actions

An action can be thought of (within Redux) as a message or something that happens (hence an action). We might write an action creator as a function, like the following

export const onLoad = (name: string) => {
  return {
    type: ActionType.Load,
    payload: name
  };
};

this returns the action which has a shape of

{ type, payload } 

The type is required (as this is what we switch in the reducer) but the payload might not be, depending upon the action.

Alternatively we might dispatch an action using the redux store, for example

store.dispatch({ type: ActionType.Load, payload: name });

The key thing when creating actions is that they should not change anything but should simply state that some action should be taken and pass any data required within the reducer.

Note: the payload can be of more complex types, usually the type is simply a string representing the name of the action.

Reducers

Reducers responds to our action messages. The usual way to write a reducer is to have a function like the following

export default (state: any = initialState, action): any => {
  switch (action.type) {
    case ActionType.Load:
      return {
        ...state,
        queryName: action.payload
      };
    // ...
  }
  return state;
}

So we simply switch, based upon the action type (hence we’d usually have the action type string as a const, which must be unique across all your application reducers, hence a style such as the following is not unusual, where we prefix the action type value with some form of namespace followed by the action name

export enum ActionType {
   Load = "myapp/load",
   // ...
}

We can clone and add different values to the state using either the spread operator, i.e.

{ ...state, queryName: action.payload }

or via

Object.assign({}, state, { queryName: action.payload }); 

Rules for reducers

  1. Must always return the state even if no changes are made an even if the state is null but must never return undefined.
  2. Treat state as immutable, hence do not change state but simply return new state (which might be cloned and with changes from the previous state).
  3. A reducer should process every action even if it just simply returns the original state.

Debugging redux

Whilst developing our UI and using redux to store our model things can get complicated when trying to understand what’s going on – did the reducer get called, what’s the state of the data in redux etc.

I’m using storybook primarily for my own testing, whilst the information below is not specific to storybook, it is a useful way to keep this stuff out of the production code.

First we need to add Chrome (or Firefox) extensions/add-ons to enable us to use the redux viewer (see https://github.com/zalmoxisus/redux-devtools-extension). When installed a Redux option should appear in the Chrome (or Firefox) debug tools and selecting it will display the redux extension which give us a way to see each dispatch message, the changes in the redux store and dig into what’s currently stored in redux.

Let’s start tings off by installing the following

yarn add redux-devtools-extension -D

Now we need to plug the dev tools into the redux store, so I’m creating a store in my story and the code looks something like this (where myReducer is obviously the reducer I’m using, initialState is obviously the state I wish to prime the redux store with, i.e. my test data). In the example below I’m using the middleware, the redux-promise-middleware this is all passed into the composeDevTools which is basically the link to the previously installed Chrome (or Firefox) dev tools.

import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(myReducer, initialState, 
  composeWithDevTools(applyMiddleware(promiseMiddleware)));

That’s all there is to it, now run your code up, display the dev tools in Chrome (or Firefox) and select the Redux option then interact with your UI to see redux dispatched message etc.

Promises within your Redux code

You’ve created your Redux reducer code and you want to get data from a remote location using fetch or axios etc. or for that matter any functionality that runs asynchronously and returns a Promise.

Let’s look at an example of some code contrived code which hopefully makes things a little clearer

export const queryDataSource = (query: string) => {
  return {
    type: ActionType.Query,
    payload: new Promise<any>((resolve, reject) => {
      // simulate a async query
      setTimeout(() => {
        resolve();
      }, 3000);
    });
  };
}

In this example code, I wanted to make it really obvious what was going on under the hood, hence we return a Promise as the payload and the promise could encapsulate a server call, but in this case we”ll simulate this with a simple timeout.

We’d now normally handle this in a switch statement like this

  switch(action.type) {
    case ActionType.Query:
      return {
        ...state,
        data: action.payload
      };

However this isn’t what we really want to happen as the payload is a Promise. What we want is to handle the change in Promise state in an asynchronous way.

Most likely we’ll want a flag in our redux data which is bound to a property that alerts the user when an asynchronous operation starts (i.e. shows a progress indicator), then alerts the user to the operation completing and also displays errors in the operation fails and ofcourse stores the results of the operation into the redux store.

This is all fairly simple to do in code using a Promise and with redux-promise-middleware it’s equally simple to do with redux. This middleware converts the Promise into three separate events which can be handled within the redux switch function.

To add redux-promise-middleware, we do the following

  • Run yarn add redux-promise-middleware
  • Next we need to add the promise middle ware to our store creation, for example using applyMiddleware(promiseMiddleware) such as the example below
    import promiseMiddleware from 'redux-promise-middleware';
    
    const store = applyMiddleware(promiseMiddleware)(createStore)(
      combineReducers({
        queryReducer,
        connectionReducer
      })
    );
    

The promise middleware will now change the name of our actions, for example assuming ActionType.Query (in the code earlier) is the string “QUERY” then the promise middleware suffixes this string with _PENDING (when the promise is first returned), _FULFILLED (when the promise successfully completes) and _REJECTED (when the promise fails, i.e. an error).

So it’s easy to write the following to use in the redux switch

const PENDING = actionType => `${actionType}_PENDING`;
const FULFILLED = actionType => `${actionType}_FULFILLED`;
const REJECTED = actionType => `${actionType}_REJECTED`;

Now our switch case statements might look like this

case PENDING(ActionType.Query):
   return {
      ...state,
      queryError: undefined,
      runningQuery: true
   };
case FULFILLED(ActionType.Query):
   return {
      ...state,
      queryResult: action.payload,
      runningQuery: false
   };  
case REJECTED(ActionType.Query):
   return {
      ...state,
      queryError: action.payload,
      runningQuery: false
   };  

In the above the PENDING action type will occur first, so we’ll ensure any error is cleared and set a property which we bind a progress indicator in the UI. If the promise fails, REJECTED is called and we could assign some error data to our store for displaying to the user, obviously FULFILLED is called when a successful completion occurs.

Getting started with Redux in React

Lot’s of Getting started posts at the moment, and probably a lot more to come. This one’s on using Redux with React and TypeScript.

Let’s create a sample application…

  • yarn create react-app {insert app name} –typescript
  • yarn add redux
  • yarn add react-redux
  • yarn add @types/react-redux
  • We can now delete the App.* files and the *.svg
  • Add folder components to src and also reducers
  • In src/components add Counter.tsx (we’re going to recreate the redux example component for in a tsx). Here’s the code
    import React, { Component } from 'react';
    
    interface CounterProps {
        value: number;
        onIncrement: () => void;
        onDecrement: () => void;
    }
    
    export default class Counter extends Component<CounterProps, {}> {
        constructor(props: CounterProps) {
            super(props);
            this.incrementAsync = this.incrementAsync.bind(this);
            this.incrementIfOdd = this.incrementIfOdd.bind(this);
        }
    
        incrementIfOdd() {
            if (this.props.value % 2 !== 0) {
              this.props.onIncrement()
            }
          }
        
        incrementAsync() {
          setTimeout(this.props.onIncrement, 1000)
        }
    
        render() {
            const { value, onIncrement, onDecrement } = this.props
            return (
              <p>
                Clicked: {value} times
                {' '}
                <button onClick={onIncrement}>
                  +
                </button>
                {' '}
                <button onClick={onDecrement}>
                  -
                </button>
                {' '}
                <button onClick={this.incrementIfOdd}>
                  Increment if odd
                </button>
                {' '}
                <button onClick={this.incrementAsync}>
                  Increment async
                </button>
              </p>
            );
        }
    }
    
  • Now in src/reducers add the file counterReducer.ts and put the following code into it
    export default (state: number = 0, action: any) => {
        switch (action.type) {
          case 'INCREMENT':
            return state + 1
          case 'DECREMENT':
            return state - 1
          default:
            return state
        }
    }
    
  • Finally, replace the contents of the index.tsx with the following
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import * as serviceWorker from './serviceWorker';
    import { Provider } from 'react-redux'
    import { createStore } from 'redux'
    import counter from './reducers';
    import Counter from './components/Counter';
    
    const store = createStore(counter);
    
    const render = () => {
        ReactDOM.render(
            <Provider store={store}>
                <Counter
                    value={store.getState() as number}
                    onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
                    onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
                />,
            </Provider>, 
            document.getElementById('root'));
    }
    
    render();
    store.subscribe(render);
    
    serviceWorker.unregister();
    
  • If everything is correct you should be able to execute yarn start and see the web page with +, – buttons etc. and clicking + or – will increment and decrement the displayed counter.

That’s a lot to take in, so what have we actually done?

Redux supplied a storage container for our application state. In the index.tsx file we create a store passing our reducer (src/reducers/counterReducer.ts) Reducers are called in response to the store.dispatch calls. In this case within index.tsx we dispatch the actions with the type set to strings INCREMENT and DECREMENT. The reducer receives these messages then makes state changes (although not actually changing the state directly but returning a new state).

As the Redux documentation on reducers states, “Remember that actions only describe what happened, but don’t describe how the application’s state changes”.

The next piece of code to look at is

store.subscribe(render);

We’ve wrapped the render code for the application in the render function. Subscribe then allows us to listen to messages on the redux store and calls the render function to render changes. The following part of the Counter code within the index.tsx file then simply assigns the current store state to the Counter value property

value={store.getState() as number

There’s not lot to really say about the Counter component which is standard React code for displaying buttons etc. and exposing properties as used in index.tsx.

More…

This is a simplistic example. Redux documentation states that it’s not advisable to write the store.subscribe code but instead use the connect function provided by React Redux for this functionality.

In this instance we create a src/containers folder and add the file CounterContainer.ts, here’s the code

import {  connect } from 'react-redux'
import Counter from './../components/Counter';

const mapStateToProps = (state: any) => {
    return {
        value: state as number
    }
}

const mapDispatchToProps = (dispatch: any) => {
    return {
        onIncrement: () => dispatch({ type: 'INCREMENT' }),
        onDecrement: () => dispatch({ type: 'DECREMENT' }),
    }
}

export const CounterLink = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);

export default CounterLink;

Here we write code to map state to properties and dispatch to properties (if we don’t handle either of these we simply supply a null in place of the function in the connect function.

As you can probably see, in mapStateToProps, the state is supplied and we simply apply it to the value property (which is on our Counter). In mapDispatchToProps we link the onIncrement and onDecrement to the dispatch functions.

Finally in index.tsx replace the Counter component with our newly created and exported CounterLink, i.e. here the new index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import counterReducer from './reducers/counterReducer';
import CounterLink from './containers/CounterContainer';

const store = createStore(counterReducer);

ReactDOM.render(
    <Provider store={store}>
        <CounterLink />
    </Provider>, 
    document.getElementById('root')
);

serviceWorker.unregister();