Introducing React and Redux

This tutorial aims to introduce you to React and Redux. We can use both to build a simple to-do list application, which you can view in your browser.

Objectives

  • Use React and then React-Redux to create a single page to-do list application.

  • Create a React component and learn how to manage state.

  • Set up a Redux store so your React components can read data from it.

React

React is an open source front-end JavaScript library used to build UIs and UI components. React is also used for state management within an application, so when your data changes React updates and renders just the specified components. You can read more about React here.

This tutorial will walk you through creating a simple React component, including some basic state management. For this, we’ll be creating a component using Class components.

We’ll create a simple list component, and then gradually improve it by adding state. We’ll follow a few steps:

  1. Getting started - Set up a project and create the component.

  2. Creating the to-do list - Create a simple component.

  3. Adding tasks - Create a task component.

  4. Adding state - Store our tasks in state.

  5. Adding a form - Create a form to add new tasks.

Getting started

Set up your project.

  1. Extract the front-end-training-main.zip file, available from your Caplin trainer.

    If you want to host your project in a different directory to the 'downloads' directory, then create a new directory and extract the ZIP file to that location.
  2. Import your project into your IDE, which is either Visual Studio Code or IntelliJ IDEA.

  3. Open a terminal in the frontend-training-main/myTradingApplication/apps/todo-list directory:

    HomeDownloadsfrontend-training-mainmyTradingApplicationappstile todo-list
  4. Run the command below to install the projects dependencies:

    $ pnpm install

Creating the to-do list

With our project set up, we can start creating our custom component.

  1. Replace the contents of frontend-training-main/myTradingApplication/apps/todo-list/src/todoList/TodoList.js with the following:

    import React, { useState } from "react";
    export default function TodoList(props) {
      const {name} = props;
      return (
        <div>
          <h1> {`${name}'s To Do List`} </h1>
          <p> Get Milk </p>
          <p> Learn React </p>
        </div>
      );
    }

    The syntax in the <h1> allows you to pass in a name property to this component, which sets the owner of the to-do list. This gives us a basic list to render, but we still need to display it.

  2. In the file frontend-training-main/myTradingApplication/apps/todo-list/src/index.js, add the following:

    import React from "react";
    import ReactDOM from "react-dom";
    import TodoList from "./todoList/TodoList";
    import "./index.less";
    function startApp() {
      ReactDOM.render(
        <TodoList name="Andy" />, // Add your own name here.
        document.getElementById("root")
      );
    }
    startApp();
    if (module.hot) {
      module.hot.accept();
    }
  3. In the frontend-training-main/myTradingApplication/apps/todo-list/index.html file check that the body includes the following tag:

    <div id="root"></div>

    ReactDOM.render requires an existing element to append your new HTML to, in this case, an element with the ID "root".

  4. Run the command below to start the app:

    $ pnpm start

You are now able to see your app by going to http://localhost:8080.

andytodolist

Adding tasks

With our to-do list created, we can refactor it to add some nicer architecture and behaviour. Let’s start by adding a list of Task components.

Create a Task.js file in frontend-training-main/mytTradingApplication/apps/todo-list/src/todoList/ with the following contents:

import React from "react";
export default function Task(props) {
  const { id, task } = props;
  return <li key={id}>{task}</li>;
}

Then we refactor our TodoList.js file to display a list of tasks instead:

import React, { Component } from "react";
import Task from "./Task";
export default function TodoList(props) {
  const {name} = props;
  return (
    <div>
      <h1> {`${name}'s To Do List`} </h1>
      <ul>
        <Task task="Get Milk"/>
        <Task task="Learn React"/>
      </ul>
    </div>
  );
}

Adding state

Our component is looking pretty good, but adding Task components to it individually isn’t ideal. Let’s refactor the TodoList.js to use a list of tasks from the state instead. Refactor our TodoList.js as follows:

import React, {useState} from "react";
import Task from "./Task";

const initialValue = [
  {id:0, value:"Get Milk"},
  {id:1, value:"Learn React"},
];

export default function TodoList(props) {
  const {name} = props;
  const[tasks, setTasks] = useState(initialValue);

  return (
    <div>
      <h1> {`${name}'s To Do List`} </h1>
      <ul>
        {tasks.map(({value, id}) => {
          return <Task key={id} task={value}/>
        })}
      </ul>
    </div>
  );
}

There are a few things going on with this change:

  • useState - We imported the useState hook from React. A hook is a special function that lets you 'hook into' React features. The useState function is a hook that lets you add React state to function components. This allows us to set up our initial state. We define our initial state outside our function with 2 tasks. Then we use useState to initialize our state and declare our state variable which we call tasks.

  • Render tasks - We use the array.map() method from JavaScript to loop through the current tasks and render a Task component for each. To ensure good performance, any time you create a list in React, you should provide a unique key to each component. If the array then changes it allows React to identify a component as the same instance from the last render.

Refresh the page and have a look at what difference your changes have made.

Adding a form

Now we’ve got the components and some basic state, let’s add a final improvement by adding a form to the page. This allows us to add new items to our to-do list. Copy the code below to TodoList.js:

import React, {useState} from "react";
import Task from "./Task";

const initialValue = [
  {id:0, value:"Get Milk" },
  {id:1, value:"Learn React" },
];

export default function TodoList({name}) {
  const[tasks, setTasks] = useState(initialValue);
  const[inputValue, setInputValue] = useState("")

  function addTask (event){
    event.preventDefault();
    setTasks(
      [...tasks, {id: tasks.length, value: inputValue},]
    );
    setInputValue("");
  }

  return (
    <div>
      <h1> {`${name}'s To Do List`} </h1>
      <ul>
        {tasks.map(({value, id}) => {
          return <Task key={id} task={value}/>
        })}
      </ul>
      <br />
      <form onSubmit={addTask}>
        <label>
          New Task:
          <input
            type="text"
            value={inputValue}
            name="inputValue"
            onChange={e => setInputValue(e.target.value)}
            placeholder="Adding a task..."
          />
        </label>
        <input type="submit" value="Add!"/>
      </form>
    </div>
  );
};

There are a few things going on with this change:

  • State - We’ve added a new piece of state inputValue which we will use later in our form.

  • We’ve added two new pieces of logic:

    • addTask - This method is used to update the list of tasks in the state. It’s called as a result of submitting the input form. We need to use event.preventDefault to prevent unintended side effects. The default behaviour of the event is to submit the form to the server, which would refresh our page.

    • onChange handler - This is a simple lambda function to update the inputValue state whenever the user types in the "New Task" input. onChange is called anytime the user changes the text in the input box.

Refresh the page again and see the new functionality you’ve added!

There’s some additional reading about components, props and hooks below:

Redux

Redux is an open source JavaScript library which focuses on managing and centralizing state. It works together with React libraries, and helps your applications to share state between components. You can read more about Redux here.

We’re starting to limit our use of Redux in our component libraries, as for streaming large amounts of data it has a big performance overhead. In addition, large parts of the benefits of state management can now be found in React hooks such as useReducer.

In this section of the tutorial, in order to provide a simple example, we’ll change our implementation of the todo-list to using Redux to manage the state.

Getting started

Set up your project. Having already completed the React tutorial above there are only a few more steps to follow to set up your project:

Redux has a couple of dependencies: redux and react-redux. These should already be installed in the todo-list project. These can be found under the dependencies of the package.json (myTradingApplication/apps/todo-list/package.json) similar to below.

"dependencies": {
  ...
  "react-redux": "^7.2.6",
  "redux": "^4.1.2"
},

Store layout

Let’s think about the Store for our app. As we’ve discussed, it’s important to create a good structure for our data. We’re going to keep the structure very simple for this app. We have a map of "todos" which are objects with an id and a value.

const store = {
    todos: [{id, value}]
}

// Initial Value:
const store = {
  todos: [
    {id: 0, value: "Get Milk"},
    {id: 1, value: "Learn React"},
  ],
};

Before we can create our store, we’re going to need some actions and reducers. Let’s create those next. Actions are events that are triggered by react code. Reducers are functions that fire on those events and modify the store.

(Optional) Advanced store layout

As our to-do items are a simple list, an array is fine. If we were building something more complex, we could use the following pattern and use a map in our store:

const store = {
  trades: {
    "a-1234": {
      amount: "1234.56",
      tenor: "spot"
    },
    "b-5678": {
      amount: "5678.91",
      tenor: "fwd"
    }
  },
  tradeIds: ["a-1234", "b-5678"]
};

This is a more scalable design that allows objects to be found via an id.

If you find you’re comfortable with the contents of this tutorial, try experimenting with your store design to explore these patterns.

Creating actions

We’re going to add one simple action. Let’s use an action creator so we have a template for creating actions later on. Make sure you add and export the constant, so the reducer can reference it.

  1. Create a new folder in apps/todo-list/src/ named redux. The directory tree should now look like this:

    HomeDownloadsfrontend-training-mainmyTradingApplicationappstodo-listsrc redux
  2. Create an actions.js file within the new folder with the following content:

    export const ADD_TODO = "ADD_TODO";
    
    export function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      };
    }

Creating reducers

Now we can create our reducers to handle our actions and produce new states.

Create a reducers.js file within the redux folder with the following content:

const initialState = {
  todos: []
};

export function todoReducer(state = initialState, action) {
  return state;
};

There are a couple interesting things to note here:

  • We’ve created an initialState and used it as the default parameter of state.

  • Our reducer currently just returns the same state it was called with.

    The reducer doesn’t currently do anything, but it will be called with undefined when the app is first created, this sets our initial state.

Adding logic to handle our action is straightforward, just make the following changes in the reducers.js file:

import { ADD_TODO } from "./actions";

const initialState = {
  todos: [
    {id: 0, value: "Get Milk"},
    {id: 1, value: "Learn React"},
  ]
};

export function todoReducer(state = initialState, action)
{
  switch (action.type) {
    case ADD_TODO:
      return {
        todos: [
          {id: state.todos.length, value: action.text},
          ...state.todos
        ],
      };
    default:
      return state;
  }
}

Again, there are few things to note here:

  • The switch statement. Switching on action like this is a common pattern for reducers.

  • The return statement for our ADD_TODO case is a little complex.

    This is because we’re ensuring we create a new state rather than mutating the existing one. This is crucial so that React knows to update!
  • You can already see how our initial store layout is presented in the todoReducer function.

Creating the store

We can now set our Store up. In the apps/todo-list/src/index.js file add the following:

import React from "react";
import ReactDOM from "react-dom";
import TodoList from "./todoList/TodoList";
import "./index.less";
import { createStore } from "redux";
import { todoReducer }  from "./todoList/redux/reducers";

const store = createStore(todoReducer);

function startApp() {
  ReactDOM.render(
      <TodoList name="Andy"/>,
    document.getElementById("root")
  );
}
startApp();
if (module.hot) {
  module.hot.accept();
}

This has created our store.

The reducer we pass in is our root reducer. All actions pass through this reducer. However, usually we’d combine several reducers into one using combineReducers or something similar, and using that as our root reducer.

Creating the provider

React-Redux gives us a <Provider> component. We can use that provider to wrap our app in, and then pass it through our store. This is very simple - just make the following changes to your apps/todo-list/src/index.js file:

import React from "react";
import ReactDOM from "react-dom";
import TodoList from "./todoList/TodoList";
import "./index.less";
import { createStore } from "redux";
import { todoReducer }  from "./todoList/redux/reducers";
import { Provider } from "react-redux";

const store = createStore(todoReducer);

function startApp() {
  ReactDOM.render(
    <Provider store={store}>
      <TodoList name="Andy"/>
    </Provider>,
    document.getElementById("root")
  );
}
startApp();
if (module.hot) {
  module.hot.accept();
}

Connecting our component to the store

To do this we use selectors, functions that take the store as a parameter and return a piece of data we’re interested in.

react-redux now provides a hook useSelector that automatically calls a selector and returns the data when the store is updated.

  1. We need to create a selector to that returns the current task list.

    Create a selectors.js file within the redux folder with the following content:

    export function getTodoList(state){
      return state.todos;
    }
  2. In apps/todo-list/src/todoList/TodoList.js, make the following changes:

    import React, { useState } from "react";
    import Task from "./Task";
    import { useSelector } from "react-redux";
    import { getTodoList } from "./redux/selectors";
    
    export default function TodoList({name}) {
      const tasks = useSelector(getTodoList);
      const [inputValue, setInputValue] = useState("");
    
      function addTask (event){
        event.preventDefault();
        setInputValue("");
      }
    
      return (
        <div>
          <h1> {`${name}'s To Do List`} </h1>
          <ul>
            {tasks.map(({value, id}) => {
              return <Task key={id} task={value}/>
            })}
          </ul>
          <br />
          <form onSubmit={addTask}>
            <label>
              New Task:
              <input
                type="text"
                value={inputValue}
                name="inputValue"
                onChange={e => setInputValue(e.target.value)}
                placeholder="Adding a task..."
              />
            </label>
            <input type="submit" value="Add!"/>
          </form>
        </div>
      );
    }

We’ve replaced the existing useState with the useSelector hook using the new selector we just added. We’ll add back in the update code to addTask in a moment.

Now that we’ve done this, we have the Redux state in out component - If you refresh the page you’ll see the list is now rendering again.

Dispatching actions

Finally, we’ll use the dispatch hook to fire our action when user presses submit.

import React, { useState } from "react";
import Task from "./Task";
import {useDispatch, useSelector} from "react-redux";
import { getTodoList } from "./redux/selectors";
import {addTodo} from "./redux/actions";

export default function TodoList({name}) {
  const dispatch = useDispatch();
  const tasks = useSelector(getTodoList);
  const [inputValue, setInputValue] = useState("");

  function addTask (event){
    event.preventDefault();
    setInputValue("");
    dispatch(addTodo(inputValue))
  }

  return (
    <div>
      <h1> {`${name}'s To Do List`} </h1>
      <ul>
        {tasks.map(({value, id}) => {
          return <Task key={id} task={value}/>
        })}
      </ul>
      <br />
      <form onSubmit={addTask}>
        <label>
          New Task:
          <input
            type="text"
            value={inputValue}
            name="inputValue"
            onChange={e => setInputValue(e.target.value)}
            placeholder="Adding a task..."
          />
        </label>
        <input type="submit" value="Add!"/>
      </form>
    </div>
  );
}

At this point we should have a working app.

Let’s go through the interesting parts of that last change:

  • We’re still using component level state, if the input value was also needed somewhere else (e.g for validation) we could also add this to the store

  • Adding an item is as simple as using dispatch(addTodo(inputValue)) - because of our action creator, we can just call one function to create and dispatch an action.

anydtodolistpt3

You’ll also notice that adding to-do list items puts them at the start of the list rather than the end. Where would you change this?

Optional exercises

If you’re already comfortable with Redux and got through this exercise quickly, you can try one or more of these extensions:

  • Refactor the app to use the advanced Store layout.

  • Create another component using the tasks state.

  • Add the ability to delete and hide tasks.