Integrating Redux in Ionic React typescript for state management

Integrating Redux in Ionic React typescript for state management

ยท

8 min read

The first question that comes to mind is what is state? Think of state as the condition of your app at any specific time. It answers questions like:

  • Is my textbox filled?
  • Is the current user logged in? A state holds variables that define an application's condition and therefore needs to be managed. That's where redux comes in.

Redux offers a predictable state container for javascript apps.

Glossary

  • Reducer: specifies state changes in response to actions performed on the app.
  • Action: Actions are payloads of information that send data from your application to your store. They are the only source of information for the store

Gettings Started/ Installations

Assumptions

  • You have working knowledge:
  • React
  • Ionic
  • Typescript
  • NPM

Start by installing Redux into your existing ionic-react app:

npm install --save redux

npm install --save react-redux

For the purpose of structure and maintainability, we would be taking an opinionated approach and hence be installing redux toolkit. Redux toolkit allows for easy debugging and helps in structuring your application store.

npm install --save @reduxjs/toolkit

npm i -D @types/react-redux

npm install --save-dev @types/webpack-env

The last command above helps fix an issue with webpack and module.hot as would be seen further down.

Our redux folder structure:

  • actions
  • reducers
  • store.ts

Let the work begin

reducers/index.ts

import { combineReducers } from "@reduxjs/toolkit";
const rootReducer = combineReducers({});
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;

This file serves as the single point of export of all the reducers. The empty object in const rootReducer = combineReducers({}); would contain the reducers to be exported.

store.ts

import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "./reducers";

const store = configureStore({
  reducer: rootReducer
});

if (process.env.NODE_ENV === "development" && module.hot) {
  module.hot.accept("./reducers", () => {
    const newRootReducer = require("./reducers").default;
    store.replaceReducer(newRootReducer);
  });
}

export type AppDispatch = typeof store.dispatch;
export default store;

This handles creating the store as well as its initialization. The first block handles bootstrapping the store with the reducer. The second block handles hot reloading/ compiling, allowing re-importing the new version of the root reducer function whenever it's been recompiled. It also tells the store to use the new version instead.

We would be making changes to the index.ts file (Entrypoint to the app) to use the store.

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";

import store from "./store";
import * as serviceWorker from "./serviceWorker";

const render = () => {
  const App = require("./App").default;
  ReactDOM.render(
    <Provider store={store}>
      <App />
    </Provider>,
    document.getElementById("root")
  );
};

render();

if (process.env.NODE_ENV === "development" && module.hot) {
  module.hot.accept("./App", render);
}

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

A provider is added to make the Redux store available to connect() calls in the component hierarchy.

Creating an action - reducer

We would be creating an action - reducer combination that controls a simple loading screen and alert dialog.

action/uiel.ts

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

interface Loader {
  isLoading: boolean;
}

interface Alert {
  isVisible: boolean;
  message: string;
}

export const setLoader = createAction("LOADER", function prepare(
  loader: Loader
) {
  return {
    payload: loader
  };
});

export const setAlert = createAction("ALERT", function prepare(alert: Alert) {
  return {
    payload: alert
  };
});

reducers/uiel.ts

import { createReducer } from "@reduxjs/toolkit";
import { setLoader, setAlert } from "../actions/uiel";

const loader = {
  isLoading: false
};
const alert = {
  isVisible: false,
  message: ""
};

export const UielReducer = createReducer(
  { loader, alert },
  {
    [setLoader.type]: (state, action) => ({
      ...state,
      loader: { ...action.payload }
    }),
    [setAlert.type]: (state, action) => ({
      ...state,
      alert: { ...action.payload }
    })
  }
);

In the createReducer method call, the first argument is a JSON object representing the initial state followed by a JSON of methods for mutation. The keys for these methods are gotten from the action types. A mutation takes the initial state and action which contains a payload as parameters.

With the action and reducer above, the reducer would be imported into reducers/index.ts in the combineReducers function call as seen below:

import { combineReducers } from '@reduxjs/toolkit';
import { UielReducer } from './uiel';

const rootReducer = combineReducers({
  uiel: UielReducer
});

export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;

Now, we have Redux setup. Let's try dispatching some actions and getting values from the state. We would be creating a HomePage component.

components/HomePage.ts

import React from 'react';
import { connect } from 'react-redux';

import { setLoader } from '../actions/uiel';


const HomePage: React.FC = (props) => {
     useEffect(() => {
          setTimeout(() => props.dispatch(setLoader({ isLoading: true })), 2000);
     },[])

     return (
          {props.isLoading?
               <div>
                    <p>The page is loading... Please wait.</p>
               </div> :
               null
          }
     )
}

export default connect((props: any) => ({
     isLoading: props.uiel.loader.isLoading
}))(HomePage);

The HomePage component's props is injected with {isLoading: props.uiel.loader.isLoading} and a dispatch object. Store value for loading can be retrieved by calling props.isLoading while an action can be dispatched by calling props.dispatch with an action passed in as a parameter as seen in the code snippet above.

If you need a website for your business, web or mobile application, head over to https://zhaptek.com and let's get talking or sign up as a referral and get 10% for every successful client referred. ๐Ÿ˜Š

Leave comments below! ๐Ÿ˜Š

Did you find this article valuable?

Support Chinaza Egbo by becoming a sponsor. Any amount is appreciated!