How does React Redux

In this workshop I give a quick and practical introduction to the state handling system Redux. For this we want our existing one React-Rewrite and expand the project from the React workshop for beginners in such a way that the state handling of our task list application is completely taken over by the Redux system and its advantages become visible in practice.

1. Why Redux?

Redux recruits State handling system with the help of which the entire program logic can be managed and thus isolated from the rest of the application. It's a stand-alone system that works without a view renderer like React can be operated.

The principle of operation is based on a central and immutable Statethat precisely defines the current status of our application. Every change to our program is represented by a very specific change to this state, so using this concept makes our application more predictable, structured and easier to test.

By using an immutable state, for example, it is also very easy to implement functionalities such as “Undo” or “Redo”, which would otherwise prove to be relatively difficult.

2. Requirements for the workshop

As with the React workshop for beginners, only basic knowledge of JavaScript and HTML needed. However, since this workshop builds on the previous React beginners workshop, the basic knowledge of React imparted there is assumed.

The latest version of the React beginners workshop is on GitHub. To make it easier to read and to make the program flow in the developer console easier to understand, I have removed all statements outside of functions and all lifecycle callback functions of the component from the source code in this project status.

Since all of the source codes required for the operation of our React Redux project are listed in full in this blog article, our application can also be implemented without having to build on the previous code base.

3. Integration of the Redux library

The Redux library and the Redux bindings for React can be integrated into our website as external JavaScript files as usual.

<!DOCTYPE html> <html> <head> <!-- default encoding --> <meta charset="UTF-8" /> <!-- external stylesheet and page icon --> <link rel="stylesheet" href="css/styles.css"> <link rel="icon" href="favicon.ico" type="image/x-icon"> <!-- library sources --> <script src="https://github.com/facebook/react/releases/download/v16.0.0/react.production.min.js" type="text/javascript"></script> <script src="https://github.com/facebook/react/releases/download/v16.0.0/react-dom.production.min.js" type="text/javascript"></script> <script src="https://fb.me/JSXTransformer-0.13.3.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js" type="text/javascript"></script> <!-- custom sources --> <script src="js/component/App.jsx" type="text/jsx"></script> <script src="js/component/TaskInput.jsx" type="text/jsx"></script> <script src="js/component/TaskList.jsx" type="text/jsx"></script> <script src="js/index.jsx" type="text/jsx"></script> </head> <body> <div id="mainContainer"></div> </body> </html>

4. Create all Redux components

In this step we want to create all the necessary modules for the administration of our global application state with the help of Redux. The three Redux elements are for this purpose State, Action and Reducer required, which are presented below.

So that we can separate these neatly in our code, we want to split them up into three new JavaScript files that we will put into our index.html include as follows:

<!DOCTYPE html> <html> <head> <!-- default encoding --> <meta charset="UTF-8" /> <!-- external stylesheet and page icon --> <link rel="stylesheet" href="css/styles.css"> <link rel="icon" href="favicon.ico" type="image/x-icon"> <!-- library sources --> <script src="https://github.com/facebook/react/releases/download/v16.0.0/react.production.min.js" type="text/javascript"></script> <script src="https://github.com/facebook/react/releases/download/v16.0.0/react-dom.production.min.js" type="text/javascript"></script> <script src="https://fb.me/JSXTransformer-0.13.3.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js" type="text/javascript"></script> <!-- custom sources --> <script src="js/redux/Action.jsx" type="text/jsx"></script> <script src="js/redux/Reducer.jsx" type="text/jsx"></script> <script src="js/redux/State.jsx" type="text/jsx"></script> <script src="js/component/App.jsx" type="text/jsx"></script> <script src="js/component/TaskInput.jsx" type="text/jsx"></script> <script src="js/component/TaskList.jsx" type="text/jsx"></script> <script src="js/index.jsx" type="text/jsx"></script> </head> <body> <div id="mainContainer"></div> </body> </html>

4.1. State

The global State can be any type under Redux. In order to create the possibility to save several values ​​in our global state, the use of an object is recommended. Only one state implementation should be used per application.

In our React application, it makes sense to move the string array, which was previously managed in the component via its variable, to the global Redux state, since this value is also transferred to the component and is therefore also used by it.

The React component addresses the two values ​​and via its variable, but these are not used outside of the component. So it doesn't make sense to transfer them to our global state.

The state managed by Redux should always be saved as read-only considered and solely by applying Actions to be changed. In this way we can exclude inconsistent and direct changes to our state object from the outset. This so-called Dispatching Actions is covered in the next two steps of this chapter.

We now want to create a new class that represents our global state and in which our string array is recorded. The structure of the class is simple because it consists of a single, non-static field and only its assignment takes place within a constructor.

We create the class with the mentioned functionality under js / redux / State.jsx:

/ ** * Defines the global application state. * * @author Christopher Stock * @version 1.0 * / class State {/ ** * Creates a new application state object. * * @param {string []} taskList The task list as an array. * / constructor (taskList = []) {this.taskList = taskList; }}

4.2. Action

A Action represents a data package under Redux that contains all the information required to change our global state. Specifically, this is a normal JavaScript object that defines a unique ID of any data type for this action in the mandatory field. Any number of additional fields can be specified as an option.

Our -Component defines four non-static methods, each of which carries out a specific manipulation of the -Array held in the state of the component. We want to define these four manipulations as redux actions.

To do this, we think of a unique ID and a corresponding function parameter for each of these four actions. To ensure good readability when debugging our action objects, we decide on a unique string for their ID and, of course, meaningful parameter names:

Action IDAction descriptionParameter nameParameter description
ACTION_CREATE_TASKCreate a tasktaskNameName of the new task
ACTION_DELETE_TASKDeleting a tasktaskIndexIndex of tasks to be deleted
ACTION_MOVE_TASK_UPPrioritizing a tasktaskIndexIndex of tasks to be prioritized
ACTION_MOVE_TASK_DOWNPrioritizing a tasktaskIndexIndex to tasks to be prioritized

Using the information gathered here, we can define our four required action objects and create correspondingly parameterized creator functions for their easy reusability.

We keep the constant action IDs as well as the class with the functions for creating our actions in the new file js / redux / Action.jsx firmly:

const ACTION_CREATE_TASK = 'ACTION_CREATE_TASK'; const ACTION_DELETE_TASK = 'ACTION_DELETE_TASK'; const ACTION_MOVE_TASK_UP = 'ACTION_MOVE_TASK_UP'; const ACTION_MOVE_TASK_DOWN = 'ACTION_MOVE_TASK_DOWN'; / ** * Specifies all redux action creators. * * @author Christopher Stock * @version 1.0 * / class Action {/ ** * Specifies the redux action for creating a task. * * @param {string} taskName The name of the task to create. * * @return {Object} The action object for creating a task. * / static createTask (taskName) {return {type: ACTION_CREATE_TASK, taskName: taskName,}} / ** * Specifies the redux action for deleting a task. * * @param {number} taskIndex The index of the task to delete. * * @return {Object} The action object for deleting a task. * / static deleteTask (taskIndex) {return {type: ACTION_DELETE_TASK, taskIndex: taskIndex,}} / ** * Specifies the redux action for moving a task up. * * @param {number} taskIndex The index of the task to move up . * * @return {Object} The action object for moving a task up. * / static moveTaskUp (taskIndex) {return {type: ACTION_MOVE_TASK_UP, taskIndex: taskIndex,}} / ** * Specifies the redux action for moving a task down . * * @param {number} taskIndex The index of the task to move down. * * @return {Object} The action object for moving a task down. * / static moveTaskDown (taskIndex) {return {type: ACTION_MOVE_TASK_DOWN, taskIndex: taskIndex,}}}

4.3. Reducer

The Reducer represents a function in which it is precisely specified which Action what change on State performs. The reducer is called by the redux system every time an action is dispatched. When the function is called, the existing state and the action to be dispatched are transferred. The reducer function then supplies the state changed by the specified action as the return value.

Since the Redux state is available as a read-only must be treated, it is imperative to always enter a new instance of the state object.

For better readability of our source code, our reducer function can also call a function specially written for the treatment of each action. These sub-functions are also referred to as reducers.

In the next step we can now put all of our logic for manipulating our array, which was previously handled in the component, into a new class under js / redux / Reducer.js outsource:

/ ** * Specifies all redux reducers. * * @author Christopher Stock * @version 1.0 * / class Reducer {/ ** * Specifies the global reducer method for the entire TaskList application. * * @param {State} state The existing state object. * @param {Object} action The action to perform on the state object. * * @return {State} The new state object. * / static globalReducer (state = new State (), action) {console.log ("Reducer.taskListReducer being invoked"); console.log ("applying action", action); console.log ("old state is", state); let newState = null; switch (action.type) {case ACTION_CREATE_TASK: {newState = Reducer.createTaskReducer (state, action); break; } case ACTION_DELETE_TASK: {newState = Reducer.deleteTaskReducer (state, action); break; } case ACTION_MOVE_TASK_UP: {newState = Reducer.moveTaskUpReducer (state, action); break; } case ACTION_MOVE_TASK_DOWN: {newState = Reducer.moveTaskDownReducer (state, action); break; } default: {newState = state; break; }} console.log ("new state is", newState); return newState; } / ** * Reduces the state in order to create a new task. * * @param {State} state The existing state object. * @param {Object} action The action to perform on the state object. * * @return {State} The new and reduced state object. * / static createTaskReducer (state, action) {let newTasks = state.taskList.slice (); newTasks.push (action.taskName); return new State (newTasks); } / ** * Reduces the state in order to delete a new task. * * @param {State} state The existing state object. * @param {Object} action The action to perform on the state object. * * @return {State} The new and reduced state object. * / static deleteTaskReducer (state, action) {let newTasks = state.taskList.slice (); newTasks.splice (action.taskIndex, 1); return new State (newTasks); } / ** * Reduces the state in order to move a task up. * * @Param {State} state The existing state object. * @param {Object} action The action to perform on the state object. * * @return {State} The new and reduced state object. * / static moveTaskUpReducer (state, action) {let newTasks = state.taskList.slice (); let taskToMoveUp = newTasks [action.taskIndex]; let taskToMoveDown = newTasks [action.taskIndex - 1]; newTasks [action.taskIndex - 1] = taskToMoveUp; newTasks [action.taskIndex] = taskToMoveDown; return new State (newTasks); } / ** * Reduces the state in order to move a task down. * * @param {State} state The existing state object. * @param {Object} action The action to perform on the state object. * * @return {State} The new and reduced state object. * / static moveTaskDownReducer (state, action) {let newTasks = state.taskList.slice (); let taskToMoveUp = newTasks [action.taskIndex + 1]; let taskToMoveDown = newTasks [action.taskIndex]; newTasks [action.taskIndex] = taskToMoveUp; newTasks [action.taskIndex + 1] = taskToMoveDown; return new State (newTasks); }}

By specifying the statement of our reducer function, we can check the status of our state at the beginning and at the end of our reducer function in the developer console when our redux system is in operation.

5. Redux store

With our three new classes for mapping the Redux components State, Action and Reducer We have now recreated our entire application logic in the structure specified by Redux and can now use the Redux system. In the first step, we want to do this completely separate from our existing React components.

The Store represents the global state container for our application. The global state is managed and actions dispatched within this store. When creating the store, only the created reducer method needs to be specified.

As soon as the Redux store has been created, an action can be applied to it using its function. To demonstrate this functionality, we can use our js / index.jsx create a Redux store and dispatch a few actions on it as a test.

// specify the application title const APPLICATION_TITLE = "React Task List"; // set page title document.title = APPLICATION_TITLE; // reference the main container let mainContainer = document.getElementById ("mainContainer"); // create redux store let store = Redux.createStore (Reducer.globalReducer); store.dispatch (Action.createTask ("Take out garbage")); store.dispatch (Action.createTask ("Washdown")); store.dispatch (Action.createTask ("Washing clothes")); store.dispatch (Action.moveTaskUp (2)); store.dispatch (Action.deleteTask (0)); // render the App component into the main container ReactDOM.render ( mainContainer);

If we test the changes made so far on our website, we can control the calls to the Redux reducer in our developer console. Here we can see that after dispatching the five actions listed, only the two to-dos “wash clothes” and “do the dishes” remain.

Expenditures of the reducer when dispatching our actions

Since we have not made any changes to our existing React components so far, they are still working at this point in time as in our last workshop and thus still display the four task items specified within the constructor of our component.

6. Connect our React project with Redux

In this chapter we want to rewrite our existing React components and their integration into the DOM in such a way that the newly defined Redux system can be used. This means that the existing application logic, which is currently still defined directly in the React components, can be completely omitted.

6.1. Use of the Redux provider

So that our React components can access the Redux store, we have to make it available via a React Redux component by placing it around the tag of our component.

The store is transferred to the React component using the attribute and is therefore available to all React components located in it. We also have this extension in our js / index.jsx by:

// specify the application title const APPLICATION_TITLE = "React Task List"; // set page title document.title = APPLICATION_TITLE; // reference the main container let mainContainer = document.getElementById ("mainContainer"); // create redux store let store = Redux.createStore (Reducer.globalReducer); store.dispatch (Action.createTask ("Take out garbage")); store.dispatch (Action.createTask ("Washdown")); store.dispatch (Action.createTask ("Washing clothes")); store.dispatch (Action.moveTaskUp (2)); store.dispatch (Action.deleteTask (0)); // render the App component into the main container ReactDOM.render ( , mainContainer);

6.2. Connect all React components

When connecting a React component to the Redux store, the two mappings and are set.

determines which values ​​of the global redux state are made available within the React component via its properties.

determines which actions of the Redux system can be dispatched as functions within the React component via its properties.

The function from the Redux framework connects a React component with exactly these two mappings and returns a connected instance of this React component.

6.2.1. Connect the TaskList component

The component used to access the string array passed to it via its property variable. In addition, three callbacks for prioritizing, lowering and deleting tasks were also called via their property variable. So we don't have to make any changes to your body when transferring the class to our Redux system.

Since the access to the string array should now take place in the field of our global redux state, we have to specify this when connecting this component using the function in the. In addition, the three defined callbacks are transferred as dispatchers of the corresponding actions via the.

Since we still want to use the component under the identifier, but it has to be given a different identifier after connecting, we simply change its original name to.

We are therefore making the following changes to the js / component / TaskList.tsx by:

/ ** * Represents the TaskList component. * * @author Christopher Stock * @version 1.0 * / class TaskListUnconnected extends React.Component {/ ** * Being invoked every time this component renders. * * @return {JSXTransformer} The rendered JSX. * / render () {console.log ("TaskList.render () being invoked"); return
    {this.createItems ()}
; } / ** * Creates and returns all items for the task list. * * @return {JSXTransformer []} The rendered JSX elements. * / createItems () {let items = []; // browse all task list items for (let index = 0; index {/ * The item description * /} {this.props.taskList [index]} {/ * Button 'Delete' * /} {/ * Button 'Move Up' * /} ); } return items; }} const taskListMapStateToProps = (state) => {return {taskList: state.taskList}}; const taskListMapDispatchToProps = {onTaskDelete: Action.deleteTask, onTaskMoveUp: Action.moveTaskUp, onTaskMoveDown: Action.moveTaskDown,}; const TaskList = ReactRedux.connect (taskListMapStateToProps, taskListMapDispatchToProps) (TaskListUnconnected);

6.2.2. Connect the TaskInput component

In our React component we only have to dispatch the action ACTION_CREATE_TASK connect with the property via the. Since we do not access the global state within this component, we do not have to map a state variable to a property and can therefore use the value zero to hand over.

Since this component is connected to the Redux system, we also have to change its original name. We do this according to the same scheme as with the component and thus change the original name of the class to.

The changes to the js / component / TaskInput.tsx look like this:

/ ** * Represents the input component that lets the user create new tasks. * This is an example for a stateful and controlled component. * * @author Christopher Stock * @version 1.0 * / class TaskInputUnconnected extends React.Component {/ ** * Initializes this component by setting the initial state. * * @param {Object} props The initial properties being passed in the component tag. * / constructor (props) {super (props); this.state = {inputError: false, inputText: "",}} / ** * Being invoked every time this component renders. * * @return {JSXTransformer} The rendered JSX. * / render () {console.log ("TaskInput.render () being invoked"); return
{this.onFormSubmit (event); }}> {/ * new task input * /} {this.onInputChange (event); }} />
{/ * new task button * /}
; } / ** * Being invoked when the input field value changes. * * @param {Event} event The event when the input field value changes. * / onInputChange (event) {this.setState ({inputError: false, inputText: event.target.value,}); } / ** * Being invoked when the form is submitted. * * @param {Event} event The form submission event. * / onFormSubmit (event) {// suppress page reload event.preventDefault (); // trim entered text let enteredText = this.state.inputText.trim (); // check entered text if (enteredText.length === 0) {// set error state this.setState ({inputError: true, inputText: "",}); } else {// clear error state this.setState ({inputError: false, inputText: "",}); // invoke parent listener this.props.onTaskCreate (enteredText); }}; } const taskInputMapStateToProps = null; const taskInputMapDispatchToProps = {onTaskCreate: Action.createTask,}; const TaskInput = ReactRedux.connect (taskInputMapStateToProps, taskInputMapDispatchToProps) (TaskInputUnconnected);

6.2.3. Connect the component App

Our component had previously managed our string array via its state variable and also forwarded this array to the component via its properties. In addition, the four callback functions,, and were defined, which were transferred to the two components and to change the state of the component.

Since the two components and communicate directly with the global state after the application logic has been outsourced to the Redux system, our component no longer has any contact with the global state. So we don't have to connect this component to the Redux system. The definition of the callbacks and their transfer to the two components can now be completely omitted.

Last but not least, the constructor with the definition of the initial state can be omitted, since this has also become obsolete due to the initial calls of the dispatcher after the store has been created.

Thus, the new source code looks like our js / component / App.jsx very clearly after the introduction of Redux:

/ ** * The entire application component. * * @author Christopher Stock * @version 1.0 * / class App extends React.Component {/ ** * Being invoked every time this component renders. * * @return {JSXTransformer} The rendered JSX. * / render () {console.log ("App.render () being invoked"); return
{/ * title * /}

{this.props.title}

{/ * task input form * /} {/ * task list * / }
; }}

7. Result

After rewriting our React components, we now have a functioning task list application again, in which the entire application logic has been outsourced to the structure predestined by Redux for state handling.

In the developer console, we can observe exactly how the state object changes when actions are dispatched by creating new tasks in our web application and deleting or re-prioritizing existing tasks.

The React-Redux library automatically ensures that corresponding changes to the state are only forwarded to the components concerned. In our application, for example, when tasks are deleted or re-prioritized, only the component is re-rendered - the component is not affected by this. Both components are only re-rendered when new tasks are created. In contrast to the previous behavior, the component is not re-rendered in any of the cases mentioned because it is not affected by any change in the global state.

The finished task list application with Redux

The finished project with all changes made is stored on GitHub.

8. "Redux DevTools" browser extension

With the extension available for Chrome "Redux DevTools“All components of the Redux system can be precisely tracked and analyzed. Thus, it is possible via the developer window of the "Redux DevTools" to take a journey through time through all logged states of the application and to examine the individual changes to the global state in detail. This enables very good debugging of our application behavior.

The finished task list application with Redux

In order for the store to be recorded by the "Redux Dev Tools" it is necessary to add the following optional second parameter when creating the Redux store:

// create redux store let store = Redux.createStore (Reducer.globalReducer, window .__ REDUX_DEVTOOLS_EXTENSION__ && window .__ REDUX_DEVTOOLS_EXTENSION __ ());

So long for now ..

I would be very happy if I could give you a quick introduction to Redux in combination with React in my workshop and use our sample application to convey the functionality and advantages of a state handling system.

As always, I am very happy to provide feedback at [email protected]

João Silas

Our React workshop