Building a React App with Redux to Manage Data Flows
What is React?
React is a JavaScript library used for creating interactive user interfaces. React is developed and maintained by Facebook as an open-source library. React documentation is comprehensive.
React has HTML, CSS, and JavaScript defined within components. HTML Layout is prescribed in a declarative style using JSX. CSS is defined using inline styles. The UI interactions are available as custom event handlers within React components.
Every React component maintains props and state. Props are passed to the component to configure the component. The state is used to change the way the component is rendered at run-time. A React component can change its state, but cannot change its props. The parent component containing the component can change the props. The component re-renders itself when the props or the state changes.
What is Redux?
React is a UI library. It is not interested in managing data flows. Redux is a library that manages data flow in a React application. Redux implements uni-directional data flow.
Actions, Store, and Reducers lie at the heart of Redux. Store is where the data resides. To update the store, React components emit actions. The store updates itself with the help of a reducer which can process the action. The updated store is available to the React components via props.
Overview of the sample application
The sample application that manages data flows we are going to build is a "Volunteer" application. Blood donors can volunteer by entering the name in the Add Donor form. The list of donors can be viewed in Donor display component. The JavaScript classes involved in the sample application are shown in the diagram below.
The top three classes, AddDonorAction
, Store
, and DonorReducer
belongs to Redux. In a typical application, there will be many actions and reducers. However, Redux allows only a single store for the data.
The bottom four classes, AppContainer
, App
, DonorForm
, and DonorDisplay
, are React components. DonorForm
has the Add Donor functionality. DonorDisplay
displays a list of donors. App is the parent component for DonorForm
and DonorDisplay
. AppContainer
has the plumbing logic for integrating React with Redux.
Building the React components
Form component
Every React component has a render function. The render function is called multiple times whenever the props or state of the component changes. The render function of the DonorForm
component is shown below:
render() {
return (
<form className="form form-horizontal" onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="name">Donor Name: </label>
<input
type="text"
className="form-control"
value={this.state.value}
onChange={this.handleChange}
/>
</div>
<div className="form-group">
<input
type="submit"
className="btn btn-primary"
value="Submit"
/>
</div>
</form>
);
}
The component has a text box and a submit button. The text box is a controlled component. The handleChange
event of the text box changes the state, and value.
The handleSubmit
function passes on the submit functionality to the parent component, App.
Display component
The display component renders all the donors. The code for the render function for the display component is shown below.
render() {
return (
<div>
{this.props.volunteers.length > 0 ? (
this.props.volunteers.map((v) => (
<div key={v.name} className="bg-info text-info volunteer">
{v.name}
</div>
))
) : null}
</div>
);
}
The list of volunteers (donors) is passed on to the display component from the App component (parent). The display component iterates over each volunteer and displays the name of the volunteer.
App component
The App component holds both the form component and the display component. The render function of the App component is shown below.
render() {
return (
<div>
<div className="jumbotron">
<h1>Volunteer</h1>
</div>
<div className="well">
<DonorForm onSubmit={this.handleSubmit} />
</div>
<div className="panel panel-primary">
<div className="panel-heading">Donors</div>
<div className="panel-body">
<PatientDisplay volunteers={this.props.donors} />
</div>
</div>
</div>
);
}
The App component handles the submit function of the form component. The App component provides an array of donors to the display component.
Building the Redux functions
Redux can be installed using npm. Both Redux and React-Redux should be installed via npm.
npm i redux react-redux --save
Actions
Actions in Redux are functions which return an action object. The action object contains the action type and the action data. We want to add a donor to the system. To add a donor, we should define addDonorAction
as follows.
export function addDonorAction(donor) {
return {
type: actionTypes.addDonor,
donor,
};
}
Reducers
Reducers in Redux are functions which update the state of the store. The store can be considered as a collection of reducers, each of which maintains its own state. To update the array of donors, we should define donorReducer
as follows.
export default function donorReducer(state = [], action) {
switch (action.type) {
case actionTypes.addDonor:
return [...state, action.donor];
default:
return state;
}
}
The reducer receives an initial state and an action. Based on the action type, it returns a new state for the store. The state maintained by reducers are immutable. We return a new state object whenever the state changes.
Store
There is only one Store while using Redux. Store is configured via the createStore
function.
export function configureStore(initialState) {
return createStore(rootReducer, initialState);
}
The createStore
function accepts a root reducer and an initial state. The initial state may come from a persistent store such as database. The root reducer is a collection of all reducers in the application.
const rootReducer = combineReducers({
donors: donorReducer,
});
Integrating React with Redux
We have defined the React components. We have defined the Redux functions. The AppContainer
is the container component which has the integration code to connect React components with Redux functions. To facilitate the integration, the React-Redux npm package is available.
The code for AppContainer
is shown below.
const mapStateToProps = (state) => {
return {
donors: state.donors,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onAddDonor: (donor) => dispatch(addDonorAction(donor)),
};
};
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(App);
The connect function in React-Redux creates the AppContainer
component. AppContainer
is a container component which has two additional props; donors
and onAddDonor
. These properties are attached with two helper functions, mapStateToProps
and mapDispatchToProps
.
The mapStateToProps
function attaches the state of the store to the props. In our example, a new props called donors
is attached. Whenever the React component uses donors, the state maintained by the corresponding reducer is returned.
The mapDispatchToProps
function attaches a function to the props which dispatches an action to the store. In our example, the onAddDonor
prop dispatches the corresponding action to the store. Dispatching an action to the store will invoke the corresponding reducer function, which will update the store data.
GitHub project
Redux introduces some complexity to a React application. Fortunately, the complexity is a one-time effort. There is a GitHub project available as a starter project. The starter project has the framework for managing data flows in a React application using Redux.
Great explanation. Thank you.