How To Handle State Management in Your React Application Using Redux
Tags: react, redux, webdev, tutorial
Published: 2023-02-07 20:14:14

State management is an essential aspect of any React application, as it determines how the different components of the app interact with each other and with the data they display. When developing large React apps, state management can be difficult and time-consuming, but libraries are available to help you handle your state management more effectively. One of these libraries is Redux. 

Redux is a library that helps organize and manage the state of a React app in a centralized and predictable way. Redux provides a centralized store for managing the state of a React application. The store holds the application's state and allows components to access and update it. This article provides information to help you handle state management effectively using Redux. 

## Setting Up Redux in Your Application

To use `redux` in a React application, you must first install the library by running the following command in your project’s directory on the terminal:

```bash
npm install redux
```

Alternatively, you run this command if you are using yarn

```bash
yarn add redux
```

After installing `redux`, you install the `react-redux` library. The `react-redux` library is a library that binds React and Redux. It allows developers to use the Redux store and its functionality in a React application. The `react-redux` library makes it easier to connect React components to the Redux store and manage the application's state. 

To install `react-redux`, run the following command:

```bash
npm install react-redux
```

Run this command, if you are making use of yarn

```bash
yarn add react-redux
```

## Creating a Store Using the createStore Function

After installing the redux library, you need to create a store. A store holds the state of your application and allows the components to access and update it. You make the store using the `createStore` function of the redux library. The `createStore` function takes in three arguments, two are optional, and one is not. The two optional arguments are the initial state and an enhancer, and the non-optional argument is a reducer function.

A reducer function is a function that takes the current state of the application and returns the new state. The reducer updates the application’s state based on the dispatched actions. A reducer function takes two arguments state and action. The `state` argument is the current state of the application, and the `action` argument is an object that describes the action that occurred, including the type of the action.

An enhancer is a higher-order function that allows you to add functionality to the store, such as middleware for handling async actions or logging. A typical example of an enhancer is middleware. A middleware is a function that takes the `store.dispatch` method as an argument and returns a new function that can be used to dispatch actions. Examples of enhancers are the `redux-thunk` middleware, `redux-logger`, `redux-devtools-extension`, etc.

```jsx
import { createStore } from 'redux';

const initialState = {
  counter: 0
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return {
        counter: state.counter + 1
      };
    case 'DECREMENT':
      return {
        counter: state.counter - 1
      };
    default:
      return state;
  }
}

const store = createStore(reducer, intialState);

export default store;
```

In the code block above, you import the `createStore` function from the redux library and use it to create a store. The `initialState` is an object containing the counter property. The `state` argument of the `reducer` function is set to `initialState` by default. 

The reducer function uses a switch statement to determine what to do based on the value of the `action.type` property. If the value of `action.type` is 'INCREMENT', the function returns a new object with the `counter` property set to the current value of the counter plus 1. If the value of `action.type` is 'DECREMENT,’ the function returns a new object with the `counter` property set to the current value of the counter minus 1.

To create actions, you will create a function `mapDispatchToProps` that takes the `dispatch` method as an argument. Then using the `dispatch` method, you will dispatch actions to the store. 

For example:

```jsx
export function mapDispatchToProps(dispatch) {
 return {
    increment: () => dispatch({type: 'INCREMENT'}),
    decrement: () => dispatch({type: 'DECREMENT'})
 }       
}
```

The `mapDispatchToProps` function returns an object with two properties, `increment`, and `decrement`, each of which is a function that dispatches an action to the store. Calling the `increment` function will dispatch an action with the type 'INCREMENT' to the store. Calling the `decrement` function will dispatch an action with the type ‘DECREMENT’ to the store.

## Connecting Your Component to the Redux Store Using the Connect Function

To connect a component to the store and access the state, you use the `connect` function from the `react-redux` library. The `connect` function takes two arguments: a function that maps the store's state to the props of the component and an object that maps action creators to the props of the component.

For example:

```jsx
import { connect } from 'react-redux';
import { mapDispatchToProps } from './actions';

function Counter(props) {
  return (
    <div>
      <h1>{props.counter}</h1>
      <button onClick={props.increment}>Increment</button>
      <button onClick={props.decrement}>Decrement</button>
    </div>
  );
}

function mapStateToProps(state) {
  return {
    counter: state.counter
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
```

The `connect` function takes two arguments: the `mapStateToProps` function that maps the store's state to the props of the component and the `mapDispatchToProps` function that returns an object that maps action creators to the props of the component. The component is then wrapped in a higher-order component (HOC) that handles the store interaction, and the resulting HOC component is returned. The component can access the state and action it needs from the store via its props.

Now you have created a store and connected your component to the store. However, for your store to be accessible to all your React components, you must utilize the `provider` component from the react-redux library.

To ensure your store is available throughout your application, you import the `provider` component from the `react-redux` library into your `app` component. The `provider` component takes the store as a prop and makes it available to all child components. To ensure your entire application has access to the store, you wrap all your components within the `provider` component.

Like so:

```jsx
import { Provider } from 'react-redux';
import store from './store';
import Counter from './counter';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}
```

## Handling Asynchronous Operations Using Redux-Thunk Middleware

The `redux-thunk` middleware is a middleware for Redux that allows you to write action creators that return a function instead of an action object. In a Redux application, action creators return an action object with a `type` and an optional `payload` property. These action objects are dispatched to the store and passed to the reducer function, which updates the application’s state.

However, in some cases, you may need to perform async operations, such as making an API call, before dispatching an action. In these cases, you use `redux-thunk` to write action creators that return a function instead of an action object. This function can then perform the async operation and dispatch the action.

## Installing and Setting Up Redux-Thunk

To use the `redux-thunk` middleware, you need to install it in your application. To install the middleware, run the following command in your project’s directory on the terminal:

```bash
npm install redux-thunk
```

After installing, you import the `thunk` function from the middleware and pass it as an argument to the `applyMiddleware` function of the redux library. The `applyMiddleware` function is a function from the redux library that allows you to apply middleware to a Redux store.

Like so:

```jsx
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { reducer } from './reducer';

const middleware = [thunk]; 

const store = createStore(reducer, applyMiddleware(...middleware));

export default store;
```

In the code block above, the `createStore` function is invoked with the `reducer` function and the `applyMiddleware` function. The `applyMiddleware` function can take multiple middlewares as arguments.

After creating the store, you create your reducer and actions:

```jsx
export function fetchData() {
  return function (dispatch) {
		fetch('https://jsonplaceholder.typicode.com/posts')
    .then( (response) => response.json() )
    .then( (data) => dispatch({
        type: 'FETCH_POSTS',
        payload: data
    }) )
	}
}
```

You created an action called `fetchData` that, when called, dispatches an action of type `FETCH_POSTS` with the payload of the data returned by the API.

Now, after creating your action, you proceed to create your reducer function:

```jsx
const initialState = {
  data: [],
};

export function reducer(state = initialState, action) {
  switch (action.type) {
    case 'FETCH_POSTS':
      return {
        data: action.payload,
      };
    default:
      return state;
  }
}
```

In the code block above, the `initialState` is an object holding a single property, `data`. The function uses the switch statement to check the `type` property of the action object.

In this case, the only action type handled is `FETCH_POSTS`. If the action's type is `FETCH_POSTS`, it will return a new state object with the `data` property set to the action’s payload. The action’s payload is the data that was fetched.

After creating your store, you connect your component to the store.

Like so:

```jsx
import React from 'react';
import { connect } from 'react-redux';
import { fetchData } from './actions';

function Home(props) {

	React.useEffect( props.fetchData, [] );
	
	return(
		{ props.data.map( (item) => ( <p key={item.id}>{item.body}</p> )) }
	)
}

const mapStateToProps = (state) => ({
	data: state.data
})

export default connect( mapStateToProps, { fetchData })(Home);
```

## Conclusion

Redux is a powerful library that can help you handle state management in your React application. By using a centralized store, actions, and reducers, you can effectively manage the state of your application. By connecting your React components to the store using the react-redux library, you can easily access the state and dispatch actions from within your components. The middleware redux-thunk help in handling the async flow of the actions.

With the help of this article, you should now have a good understanding of how to set up and use Redux in your React application and how it can help you handle state management in a more efficient and manageable way.