In this article, we will solidify our understanding of the core concepts of Redux building a CRUD application. We will go step by step to build a note-taking application integrating redux for state management. In the note-taking application, users can create, read, edit, and delete the notes. 

PREREQUISITE

  • Basic understanding of JavaScript ES6
  • Basic understanding of HTML and CSS
  • Prior understanding of react
  • Have Nodejs installed on your machine

Let’s get started as we create a new project using the create-react-app so go to a directory where you will store this project and type the following in the terminal

create-react-app note-redux-app

The above command uses the create-react-app CLI tool to generate a react boilerplate project for us so that we don’t have to configure any tooling. 

For the command above to work, the create-react-app CLI tool must be installed globally using the command below.

npm install -g create-react-app

This will generate a react scaffold for our project with all the basic dependencies installed and webpack setup. 

Change your directory to the root folder of our project and install the react-redux dependency

cd note-redux-app
npm install react-redux && redux

Let’s spin up the server for our react project 

npm start

What is Redux?

From the redux official documentation, Redux is a predictable state container for all JavaScript applications.  In simplified terms, Redux is a library that makes state management easier not just for react but for any tool used but it works really well with react. gives you access to the state anywhere in your components without the need to pass props. In redux, the entire state of our application lives in the store. Instead of having to manage the state in different components, have to only manage it in the store.

A store is an object which has some methods in it that allow us to get the current state of our application, subscribe to changes, or update the existing state of our application. This is great because now we don’t have to pass down data from the parent component to deeply nested child components through props. So irrespective of how deeply nested a child component could be, it can access data directly from the store at any time.

What is React Redux?

React Redux is the official React binding for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update data.

Learning React: Functional Web Development with React and Redux

With this initial understanding of redux and react with redux, let’s delete some files that are not necessary for this project. These files include (App.css, App.test.js, logo.svg and registerServiceWorker.js)

Let’s make the following change to the index.js file

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App/>, document.getElementById('root'));

 In the src folder, create a folder called components with the following files NewNote.js, Note.js, NoteCount.js, EditComponent.js and NoteList.js files.

 Add the snippet below to the NewNote.js file.

import React, {Component} from 'react';
class NewNote extends Component {
    render() {
        return (
            <div className="note-container">
                <h1 className="note_heading">Create Note</h1>
                <form className="form">
                    <input required type="text"
                           placeholder="Enter Note Title"/>
                    <br/><br/>
                    <textarea required rows="5" cols="28"
                              placeholder="Enter Note"/>
                    <br/><br/>
                    <button>Note</button>
                </form>
            </div>
        );
    }
}
export default NewNote;

Add the code snippet below to the NoteList component.

import React, {Component} from 'react';


class NoteList extends Component {
    render() {
        return (
            <div>
                <h1 className="note_heading">All Notes</h1>
            </div>
        );
    }
}
export default NoteList;

Since this is not a CSS tutorial as we are more focused on building the logic for our project, replace the content of the index.css file with the code snippet below and that will be enough for our styling.

body {
    margin: 0;
    padding: 0;
    font-family: 'Work Sans',sans-serif;
    background:#eff3f4;
}


.center {
    text-align: center;
}
a {
    color:#636363;
    text-decoration: none;
    transition:all 0.5s ease-in;
}


a:hover {
    text-decoration: underline;
}




/* note Styles */
.note-container {
    background:#fff;
    padding:50px;
    width:100%;
    margin:40px auto;
    box-shadow:0 5px 15px 0 rgba(46, 61, 73, 0.12);
    font-family:'Work Sans',sans-serif;
}


.note_heading {
    text-align:center;
    font-weight:400;
    font-size:40px;
    color:#636363;
}


.form {
    display:flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}


.form input {
    height:44px;
    padding-left:15px;
    padding-right:15px;
    border: 1px solid #dbe2e8;
    font-size:14px;
    border-radius:2px;
    color:#636363;
    box-shadow: 0 2px 2px 0 rgba(46, 60, 73, 0.05);
    outline:none;
    width:80%;
}


.form textarea {
    width:80%;
    max-width:80%;
    padding-left:15px;
    padding-right:15px;
    padding-top:15px;
    border: 1px solid #dbe2e8;
    font-size:14px;
    border-radius:2px;
    color:#636363;
    font-family: 'Work Sans,',sans-serif;
    box-shadow: 0 2px 2px 0 rgba(46, 60, 73, 0.05);
    outline:none;
}


/* All notes */


.all_note_heading {
    text-align:center;
    font-weight:400;
    font-size:40px;
    color:#636363;
    word-wrap: break-word;
}


.note {
    background:#02b3e4;
    width:100%;
    margin:0 auto;
    padding:5px;
    margin-bottom:10px;
    box-shadow: 10px 10px 12px 0 rgba(46, 60, 73, 0.05);
}


.form button {
    background:#2402e4;
    padding:15px 40px;
    text-align: center;
    color:#fff;
    font-family:'Work Sans',sans-serif;
    border-radius:4px;
    border:none;
    font-size:20px;
}


.note_title {
    font-weight:400;
    text-align: center;
    font-size:20px;
    color:#636363;
    word-wrap: break-word;
}


.note_message {
    font-size:18px;
    font-weight: 200;
    line-height: 1.5rem;
    word-wrap: break-word
}


.control-buttons {
    display:flex;
    justify-content: space-between;
}


.delete {
    background: #ff7777;
    border:none;
    text-align: center;
    font-size:15px;
    padding:5px 5px;
    cursor:pointer;
    outline: none;
    color:#fff;
}


.edit {
    background:#1102e4;
    border:none;
    text-align: center;
    font-size:15px;
    padding:5px 5px;
    cursor:pointer;
    outline: none;
    color:#fff;
}

Let’s see the outcome of our code so far.

npm start

Now we have our basic user interface completed. Let’s get started adding some redux functionalities to our project. Before writing more lines of codes to our project, let’s get a basic understanding of the redux core concepts.

Store:

Every redux application has one thing in common and it is known as the store. It’s where the entire state of the redux application lives. Instead of having to manage the state in different components, we have to only manage it in one single place called the store. Thus making our state predictable. 

Reducers:

These specify how the application’s state changes in response to https://redux.js.org/basics/actionsactions sent to the store.

Actions:

These are payloads of information that send data from your application to your store. They are the only source of information for the store. Actions are nothing but plain JavaScript objects with the type of property. You send them to the store using https://redux.js.org/api/store#dispatchactionstore.dispatch().

Eg.

const ADD_TODO = 'ADD_TODO'
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

store.dispatch():

The dispatch method accepts an object as its argument and this object is called an ‘action’.

With that understanding in mind, we will create the reducers for our application.

In the src folder, create a folder called reducers with noteReducer.js file and add the code snippet below to it.

const noteReducer = (state = [], action) => {
    switch (action.type) {
        case 'ADD_NOTE':
            return state.concat([action.data]);
        case 'DELETE_NOTE':
            return state.filter((note) => note.id !== action.id);
        case 'EDIT_NOTE':
            return state.map((note) => note.id === action.id ? {...note, editing: !note.editing} : note);
        case 'UPDATE':
            return state.map((note) => {
                if (note.id === action.id) {
                    return {
                        ...note,
                        title: action.data.newTitle,
                        message: action.data.newMessage,
                        editing: !note.editing
                    }
                } else {
                    return note;
                }
            });
        default:
            return state;
    }
};
export default noteReducer;

Each time an action is dispatched from any component that is connected to the store, the reducer receives the action and test the type property of the action for each of these cases and returns the current state of the test does not meet any of these cases.

Also, we will create the store by making the following changes to our index.js file.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import noteReducer from './reducers/noteReducer'
import App from './App';
const store = createStore(noteReducer);
ReactDOM.render(<App/>, document.getElementById('root'));

Connecting the Redux Store to Our React Application

Up until now, our react application does not have access to the store. In order to make we react app to access the store, react and redux must be connected using the provider function from the react-redux package.

import {Provider} from 'react-redux';

Finally, our app.js file should contain the following code snippet

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import noteReducer from './reducers/noteReducer'
import App from './App';
const store = createStore(noteReducer);
ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>, document.getElementById('root'));

Create New Note

Now we want every new note created to be added to the store and as such, we need to connect our NewNote.js component to the store by updating it with the following code snippet.

import React, {Component} from 'react';
import {connect} from 'react-redux';
class NewNote extends Component {
    handleSubmit = (e) => {
        e.preventDefault();
        const title = this.getTitle.value;
        const message = this.getMessage.value;
        const data = {
            id: new Date(),
            title,
            message,
            editing: false
        };
        this.props.dispatch({
            type: 'ADD_NOTE',
            data
        });
        this.getTitle.value = '';
        this.getMessage.value = '';
    };
    render() {
        return (
            <div className="note-container">
                <h1 className="note_heading">Create Note</h1>
                <form className="form" onSubmit={this.handleSubmit}>
                    <input required type="text" ref={(input) => this.getTitle = input}
                           placeholder="Enter Note Title"/>
                    <br/><br/>
                    <textarea required rows="5" ref={(input) => this.getMessage = input} cols="28"
                              placeholder="Enter Note"/>
                    <br/><br/>
                    <button>Note</button>
                </form>
            </div>
        );
    }
}
export default connect()(NewNote);

The connect method in the code snippet above allows the NewNote component to access the dispatch method from the store. We need dispatch whenever we want to pass some action to the reducer then depending on the type property of the action, the reducer can decide what to do with the state. It can give you access to your state which is living inside your store object through the mapStateToProps method.

When the Note button is clicked, the form’s onSubmit event is triggered and the handleSubmit function is invoked. The handleSubmit function takes one argument which is the event. The e.preventDefault() method prevents the page from refreshing. Next, we grab the value of the title and the message from the inputs using refs and then put them inside an object called data. We also have an id property whose value is set to whatever new Date() returns. The id property will be used to perform an update and delete operations. We dispatch an action with type ‘ADD_NOTE’ to the reducer which adds the new note to the store through the ADD_NOTE’ case in the reducer’s switch statement.

case 'ADD_NOTE':
            return state.concat([action.data]);

Finally, we clear the input fields.

Now, we should be able to add a new note to the store.

N/B the editing property will be required for the edit functionality of our crud app. Since we are creating a new note editing property is set to false.  

Total Number of Notes

To display the total number of notes on the browser, Let’s create a NoteCounter component. In the components folder, create a NoteCounter.js file and add the following code.

import React, {Component} from 'react';
import {connect} from 'react-redux';
class NoteCounter extends Component {
    render() {
        return (
            <p className="note_message">Number of notes: {this.props.numberOfNotes}</p>
        );
    }
}
const mapStateToProps = (state) => {
    return {
        numberOfNotes: state.length
    }
};


export default connect(mapStateToProps)(NoteCounter);

List all Notes

In order to get the list of all our notes, we will update our NoteList.js component with the following code snippet.

import React, {Component} from 'react';
import {connect} from 'react-redux';
import Note from './Note';


class NoteList extends Component {
    render() {
        return (
            <div>
                <h1 className="note_heading">All Notes</h1>
                {this.props.notes.map((note) => (
                            <Note key={note.id} note={note}/>
                ))}
            </div>
        );
    }
}
const mapStateToProps = (state) => {
    return {
        notes: state
    }
};


export default connect(mapStateToProps)(NoteList);

The mapStateToProps method gives the NoteList component access to the current state in the store. Now we have access to all the notes in the store and we can now display each note component using the array.prototype.map() method.

For our notes to be seen in the browser, we will create a note component. In our components folder, create a Note.js file and add the code snippet below to it.

import React, {Component} from 'react';
class Note extends Component {
    render() {
        return (
            <div className="note">
                <h2 className="note_title">{this.props.note.title}</h2>
                <p className="note_message">{this.props.note.message}</p>
                <div className="control-buttons">
                    <button className="edit">Edit</button>
                    <button className="delete">Delete</button>
                </div>
            </div>
        )
    }
}
export default Note;

The Note component receives the note object as a prop from the NoteList component. Now our app should look like this

Deleting a Note

Let us add the delete functionality to the delete button in the note component. Now we will see the actual benefit of redux as the Note component which is a child component to the NoteList component will make changes directly to the store. In the Note component, the delete button should be as the following snippet

<button className="delete" 
  onClick={() => this.props.dispatch({type: 'DELETE_NOTE', id: this.props.note.id})}
>
  Delete
</button>

Here, we dispatched an action with type ‘DELETE_NOTE’ to the reducer which then updates the store through the ‘DELETE_NOTE’ case in the reducer switch statement.

case 'DELETE_NOTE':
            return state.filter((note) => note.id !== action.id);

With that in place, each note items can be deleted. 

Editing a Note

We will create a new component to support the edit functionality. In the component folder, let’s create an EditNote.js file with the following code.

import React, {Component} from 'react';
import {connect} from 'react-redux';
class EditNote extends Component {
    render() {
        return (
            <div key={this.props.note.id} className="note">
                <form className="form">
                    <input required type="text" ref={(input) => this.getTitle = input}
                           defaultValue={this.props.note.title}
                           placeholder="Enter Note Title"/>
                    <br/>
                    <br/>
                    <textarea required rows="5" cols="28" ref={(input) => this.getMessage = input}
                              defaultValue={this.props.note.message} placeholder="Enter Note"/>
                    <br/>
                    <br/>
                    <button>Update</button>
                </form>
            </div>
        );
    }
}
export default connect()(EditNote);

At this point, our EditNote component is just a basic form.

We need to update the NoteList component in order to display the EditNote component in the browser. so let’s head back to the NoteList component and update it with the following piece of code.

import React, {Component} from 'react';
import {connect} from 'react-redux';
import Note from './Note';
import EditComponent from './EditNote';


class NoteList extends Component {
    render() {
        return (
            <div>
                <h1 className="note_heading">All Notes</h1>
                {this.props.notes.map((note) => (
                    <div key={note.id}>
                        {note.editing ? <EditComponent note={note} key={note.id}/> :
                            <Note key={note.id} note={note}/>}
                    </div>
                ))}
            </div>
        );
    }
}
const mapStateToProps = (state) => {
    return {
        notes: state
    }
};


export default connect(mapStateToProps)(NoteList);

Now the EditNote component will be conditionally rendered if the editing property in the store is true.  

Now we have a way to render the edit component so let’s get back to our Note component and dispatch an action to the store which will set the editing property to true once the edit button is clicked.

<button className="edit" onClick={() => this.props.dispatch({type: 'EDIT_NOTE', id: this.props.note.id})}>Edit
                    </button>

The code snippet above dispatch an action with type ‘EDIT_NOTE’ and a payload of id to the reducer which then updates the store through the edit ‘EDIT_NOTE’ case in the reducers switch statement.

case 'EDIT_NOTE':
            return state.map((note) => note.id === action.id ? {...note, editing: !note.editing} : note);

The code snippet above checks the current state in the store for a note with the same id as that received from the payload and if the id is found, the note object is returned with the editing value of the note set to true if it was false and vice-versa. Otherwise, the note object is returned.

Now let’s make the update button in the EditNote component to perform the update functionality. Let us update the EditNote component with the following 

import React, {Component} from 'react';
import {connect} from 'react-redux';
class EditNote extends Component {
    handleUpdate = (e) => {
        e.preventDefault();
        const newTitle = this.getTitle.value;
        const newMessage = this.getMessage.value;
        const data = {
            newTitle,
            newMessage
        };
        this.props.dispatch({type: 'UPDATE_NOTE', id: this.props.note.id, data: data});
    };
    render() {
        return (
            <div key={this.props.note.id} className="note">
                <form className="form" onSubmit={this.handleUpdate}>
                    <input required type="text" ref={(input) => this.getTitle = input}
                           defaultValue={this.props.note.title}
                           placeholder="Enter Note Title"/>
                    <br/>
                    <br/>
                    <textarea required rows="5" cols="28" ref={(input) => this.getMessage = input}
                              defaultValue={this.props.note.message} placeholder="Enter Note"/>
                    <br/>
                    <br/>
                    <button>Update</button>
                </form>
            </div>
        );
    }
}
export default connect()(EditNote);

Once the update button is clicked, the form will be submitted which will then trigger the onSubmit event to invoke the handleUpdate method. The handleUpdate method dispatch an action with type ‘UPDATE_NOTE’ and a payload of id and data to the reducer which then updates the store through the update ‘UPDATE_NOTE’ case in the reducers switch statement.

case 'UPDATE_NOTE':
            return state.map((note) => {
                if (note.id === action.id) {
                    return {
                        ...note,
                        title: action.data.newTitle,
                        message: action.data.newMessage,
                        editing: !note.editing
                    }
                } else {
                    return note;
                }
            });

The code snippet above uses the Array.prototype.map() method to iterate through all the note objects in the store checking for a note whose id is equal to the id of the action. if found, the note state will be updated. Otherwise, a note object is returned.

With this, each functionality of our redux note application is completed as we can now create notes, read, edit, and delete our notes.

Conclusion

In this article, we demonstrated how to manage state in our react application using redux by building a functional note-taking application to solidify our understanding of the redux concepts. I hope the concepts we went over help you understand state management in react using the redux library. For more information on redux, you can check out the official redux documentation here. Most importantly, I hope this tutorial has shed some light on the benefits of using redux for state management in your react applications. You can find the complete project for this article on Github. You can also check our beautiful MaterialPro React Admin and check our great note application and much more.