Honestly, this blogpost was a long time coming, I have been working on/off with react for the last 2 years and this time, I feel it is time to stick with it and not leave until I gain a full understanding of it. So here goes, and as with all my posts the final codebase for this post is on Github. You will see the Github link as you read on. This blogpost is about how to build a note taking app with react while using Redux Store to manage state. In other words, this post is a tutorial on how to build a react app with redux store for state management that provides users an input modal form for data entry and an accordion to expand entries.

Background

The last 3 months have been very challenging for me personally. My emotional state was and somewhat is, just all over the place, which means I was not in a state of mind to write a new post. Now that things are a bit better, I thought it would be a good time to get back to the things I love which includes sharing knowledge by writing a new blog post.

Over the last few tears, my full stack experience involved working with Angular and not React, as it stands I still love Angular. However, in the last 2 years, I have worked with React on one-off projects, one of them was when I tried to compare the the two frameworks. You can read my post on it below.

In addition to the above, I used it every now and then in my last job as well as built react apps for the so-called “tech tests” for job interviews. The way it has played out so far is, I start working on react based apps and then I move to working on other stuff. The last time, I had a serious go at Rect prior to this was when I tried building an app for Covid case numbers. You can read my blogpost below.

Anyway, this time, I am determined to give this a serious go and actually learn to work with react properly. Especially since my team at the bank is missing a React engineer and I aim to fill in the void.

Problem

Build a simple MVP for a note taking app which shows a list of notes where each note has a title and the actual note. The list initially shows the titles of the notes and if the user clicks on a note, it is expands to revel the contents of the notes. The user should also have the ability to add new notes.

Solution: CRUD react app with redux store for state management

More than anything, the goal of this exercise is to try and understand how different pieces of a front-end react app work together. Based on the aforementioned problem description, you can guess that it’s an app that would have

  • A form for user input
  • A storage mechanism to store the data
  • Accordion to expand and collapse the views

Let us start by creating a simple react app.

npx create-react-app react-accordion-demo

Initial rendering, expand and collapse notes

Next, let’s build something that shows a list of notes by modifying the contents of the app.js file to this.

import React from 'react';

const App = () => {
  const accordionData = {
    title: 'Section 31',
    content: `Section 31 from star trek is a classified area with top secret
    experiments conducted by the federation and is considered illegal. Is it though?
 Maybe or maybe not but still we've now made an really long conversation about it. Which serves this tutorials purpose`
  };

  const { title, content } = accordionData;

  return (
    <React.Fragment>
      <h1>React Note Taking Accordion </h1>
      <div className="accordion">
        <div className="accordion-item">
          <div className="accordion-title">
            <div>{title}</div>
            <div>+</div>
          </div>
          <div className="accordion-title">{content}</div>
        </div>
      </div>
    </React.Fragment>
  );
};

export default App;

Also, create another file in the src/ and call it style.css. With the following contents,

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Verdana, Geneva, Tahoma, sans-serif;
  font-size: 20px;
  background: rgb(238, 174, 202);
  background: radial-gradient(
    circle,
    rgba(238, 174, 202, 1) 0%,
    rgba(199, 233, 148, 1) 100%
  );
}

h1 {
  text-align: center;
  margin: 2rem 0 4rem 0;
}

.accordion {
  max-width: 600px;
  margin: 2rem auto;
}

.accordion-title {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  cursor: pointer;
  background-color: #21aeca;
}

.accordion-title:hover {
  background-color: #3ab4cc;
}

.accordion-title,
.accordion-content {
  padding: 1rem;
}

.accordion-content {
  background-color: #39b9d2;
}

@media screen and (max-width: 700px) {
  body {
    font-size: 18px;
  }

  .accordion {
    width: 90%;
  }
}

For now, the accordion data (notes) for this app is hard-coded i.e. it’s static and now dynamic. Also take “note” (ha – see what I did there) of the fact that we are not declaring a class here. We are creating a variable and exporting it. How this changes the code is that all variables declared inside are in block scope and you can refer to them without using “this” reference. To maintain state here, you would need to use React.hooks. Anyway, to use app in the main class, modify the contents of your index.js to this

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import './styles.css';

ReactDOM.render(
      <App/>,
    document.getElementById('root')
);

Now if you run your react app with npm start you should see a basic app with an accordion that can be expanded to show notes. This is a great first step and we have built a basic react app that shows some data. However at this point we only have 1 note, when using an app like this, the user will add multiple notes. So what then?

Make notes expand and collapse

Right now all the data is displayed in it’s expanded state and clicking the ‘+’ symbol does not have any effect. To implement the behaviour of expanding and collapsing, notes, we will have to use React hooks. If you recall, they were mentioned earlier in this post and that hooks are a way to manage state for non class based React components. Let us import useState from the react library.

import React, { useState } from 'react';

In case you are wondering, why have React outside of curly braces but useState in curly braces e.g. { useState }. It’s because, you can have only one default export from a module which in the above code is React, and for any other exports from the module, they need to be imported by having them in curly braces. To use them in the code above simply modify the code inside the accordion-item div with the following

    <div
      className="accordion-title"
      onClick={() => setIsActive(!isActive)}
    >
      <div>{title}</div>
      <div>{isActive ? '-' : '+'}</div>
    </div>
    {isActive && <div className="accordion-content">{content}</div>}

In react you can bind events to elements using the curly braces (onClick={() => setIsActive(!isActive)}) and in the above code, we are declaring an event handler for the onclick event using arrow functions. Above we are simply calling the setIsActive method that we defined using React.hooks and setting the isActive flag to the opposite of what it was.

Now in app.js in the line above the { title, content } declaration, set the initial state of the accordion.

const [isActive, setIsActive] = useState(false);

Displaying multiple notes

Before doing this, let’s do one more thing and that is separate out the accordion bits into it’s own little file that we can use anywhere in the app. In the src/ create another file called accordion.js and add the following code to it.

import React, {useState} from 'react';

const Accordion = ({title, content}) => {
    const [isActive, setIsActive ] = useState(false);

    return (
        <div className='accordion-item'>
            <div className='accordion-title' onClick={() => setIsActive(!isActive)}>
                <div>{title}</div>
                <div> {isActive ? '-' : '+'} </div>
            </div>
            {isActive && <div className='accordion-content'> {content} </div>}
        </div>
    );
};

export default Accordion;

In Angular, you create components and inject them as dependencies whereas in React you export components and import them where you want to use them. To use this in app.js, first import the accordion in the app.js file

import Accordion from './Accordion';

and replace the code in the div with class accordion as follows

      <div className="accordion">
        {accordionData.map(({ title, content }) => (
          <Accordion title={title} content={content} />
        ))}
      </div>

Now if you refresh it, you will probably see an error, why? it is because the code above expects the an array but for accordionData we only have an object. Hence let’s fix that, create a new file in src folder called data.js and replace it with the following contents.


const data = [{
    title: 'Section 31',
    content: `Section 31 from star trek is a classified area with top secret
    experiments conducted by the federation and is considered illegal. Is it thought?
    Maybe or maybe not but still we've now made an really long conversation about it.
    Which serves this tutorials purpose`
  }, {
    title: 'Section 32',
    content: `Section 32 from star trek is a classified area with top secret
    experiments conducted by the federation and is considered illegal. Not true, 
    no such place exists. Just an elaborate joke to fill up text`
  }
];
export default data;

In the app.js file import data

import data from './data';

and initialise the accordionData variable with it

const accordionData = data;

The next step is for the user to be able to enter new notes it.

Note: parts of this app were built by following this tutorial here.

Storing and retrieving new data

For this one, we have a few different problems to solve, so let us follow a structured approach.

Storage

The goal of this tutorial is to understand react front-end side therefore we will not be building any Java or NodeJS backend here.

Now that the initial disclaimer is done, there are a few ways in which we can store and retrieve this information using client-side storage. IndexedDB comes to mind and so do all these other alternatives, however for this one let us keep it simple and use LocalStorage. Stringifying, parsing, storing and retrieving information from Local Storage is fairly self-explanatory. The challenge here is once, the user saves new information, how do we refresh the list with the newly added info?

Managing state

A refresh button in the UI would solve this problem, but it is the year 2022 and we can do better. If you think about it, this is essentially a state management problem. Our app has a list of notes and when the user adds a new note, it is added to the existing list. Once added, the list displayed in the UI should show the newly added item. So the problem here is managing the state of the list that is displayed in the UI. State management across different parts i.e. keeping data in sync across all areas of the app’s UI can be challenging but fortunately the Redux library can help with that.

Redux setup

Since we are going to be using the react-redux flavor for our react app, installing and setting up redux is fairly straight forward.

npm install react-redux

Once installed add a new file in the src directory called reduxStore.js and add the following code to it

import { createStore } from "@reduxjs/toolkit";

/**
 * the action payload will have details of the form
 * @param {*} state 
 * @param {*} action 
 * @returns 
 */
const accordionReducer = (state = [], action) => {
    if(localStorage.getItem("data").length > 0) {
        state = JSON.parse(localStorage.getItem("data"));
    }
    if(action.type = "ADD" && action.payload) {
        state = [];
        if(localStorage.getItem("data").length > 0) {
            state = JSON.parse(localStorage.getItem("data"));
        }
        state.push(action.payload);
        localStorage.setItem('data', JSON.stringify(state));
    } else if(action.type == "DELETE") {
        //  to be implemented
    } else if(action.type == "UPDATE") {
        //  to be implemented
    }
    return state;
};

export const dataStore = createStore(accordionReducer, []);

The above code does the following,

  • Define a reducer that manages app state
  • The state is an array of objects i.e. our data, which in this example are notes
  • State is first initialised with the data saved in LocalStorage
    • LocalStorage as it gives the simplest solution for this scenario
  • The reducer tracks various actions dispatched via the UI
  • An action is a plain Javascript field which has a type and a payload. The payload can be the data dispatched and the type could be the action dispatched
  • In the code above we only track (do something) the add action where we take the payload and add it to the current state and save it to local storage
  • Delete and Update have not been implemented yet
  • A read operation is as simple as retrieving the current state which in our case is an array of note objects

For this please “make a note” (ha – I did it again lol ) that this redux store will manage the data between components. To update the state in Redux we need to dispatch actions to it but before we get to that, let us change topics a little and see how will we build a React form for user entry.

Building an input form

For this app, we want the users to be able to see the form as a popup or modal such that it overlays the existing list. In your src directory create a directory called components and add a new file called modal.js. Replace it with the code below.

import React from "react";
import PropTypes from 'prop-types';
import "./modal.css";
import { dataStore } from "../reduxStore";

export default class Modal extends React.Component {

    state = {
        name: "It's name?",
        text: "Type your text here"
    }
    formElemClick = (e) => {
        if((e.target.id === "newEntryTitle"
        && e.target.value === this.state.name)
        || (e.target.id === "newEntryText"
        && e.target.value === this.state.text)) {
            e.target.value = "";    
        }
    }

    handleSubmit = (e) => {
        const newContent = {
            title: e.target[0].value,
            content: e.target[1].value
        };
        dataStore.dispatch({
            type: "ADD",
            payload: newContent
        });
    }

    onClose = e => {
        this.props.onClose && this.props.onClose(e);
    };
    render() {
        if(!this.props.show) {
            return null;
        }
        return (
            <div className="modal">
                <form onSubmit={this.handleSubmit}>
                    <div style={{ paddingTop: 20 }} >
                        <div>New Content</div>
                        <div> 
                            <input placeholder={this.state.name} 
                                onChange={this.valueChanged}
                                defaultValue={this.state.name} 
                                onClick={this.formElemClick}
                                id="newEntryTitle"></input> 
                        </div>
                        <div>
                            <textarea onChange={this.valueChanged}
                                onClick={this.formElemClick}
                                placeholder={this.state.text}
                                defaultValue={this.state.text} 
                                rows={5} cols={40} id="newEntryText"></textarea>
                        </div>
                        <div className="addFormBtnContainer">
                            <button type="submit" className="button">Save Entry</button>
                            <button className="button" onClick={this.onClose}>
                                Close Modal
                            </button>
                        </div>
                    </div>
                </form>
            </div>
        );
    }
}
Modal.propTypes = {
    onClose: PropTypes.func.isRequired,
    show: PropTypes.bool.isRequired
};

To better understand the code above, have a look at the code in the return function. A lot of the code above is self-explanatory so let us focus on understanding the overall gist of it.

Input form summary

As you can see, it is just plain old html code with references to the React methods in the class.

<input placeholder={this.state.name} 
          onChange={this.valueChanged}
          defaultValue={this.state.name} 
          onClick={this.formElemClick}
          id="newEntryTitle">
</input> 

Notice how the code above uses the “this” reference, it is because it is a class based component so we do not need to use React hooks to manage state. Here we assign properties to the component’s state and use the to manage the values in the form (two day data binding). Next, let’s look at the handle submit method

handleSubmit = (e) => {
        const newContent = {
            title: e.target[0].value,
            content: e.target[1].value
        };
        dataStore.dispatch({
            type: "ADD",
            payload: newContent
        });
    }

The handleSubmit is called when the user submits the form, i.e.

<form onSubmit={this.handleSubmit}>

As such the when the handleSubmit method is called, the event target is a form and the values of the fields are in the values array.

The only thing that is “special” about the handle submit method above is the datastore.dispatch({}) bit which broadcasts the “add” event to the rest of the app and attaches a payload with it (the payload is a note object). More on who or what is notified by this event a bit later. Now, how would this be a popup? or what makes it a modal.

How is the above form a popup?

For this let us look at the css for it

.modal {
  display: flex;
  justify-content: center;
  width: 500px;
  background: white;
  border: 1px solid #ccc;
  transition: 1.1s ease-out;
  box-shadow: -2rem 2rem 2rem rgba(black, 0.2);
  filter: blur(0);
  transform: scale(1);
  opacity: 1;
  visibility: visible;
  position: absolute;
  margin-left: auto;
  margin-right: auto;
  top: 250px;
  border-radius: 10px;
}

In all those properties defined in a class, notice position: absolute what that does is that it removes the element from the normal document flow such that it does not affect the position of any other elements. What this means is that the position of this document is relative itself based on the top, left, bottom and right. So other elements in the DOM do not affect the positioning of this.

How do we close the input form?

First let us look at the onClose method that is defined in the class.

onClose = e => {
    this.props.onClose && this.props.onClose(e);
}

What the close above means is that the onClose event is passed into this component from the uses this component. Ok to understand it in a non-technical terms, most of the time when you see props.property in react, it means that the property or an event is being passed into the component. We will see how to use the Modal component in our react app but just one more thing before we do that.

What are propTypes?

Notice, propTypes in the end? As you can guess, it is how you can enforce type checking with React. I reckon this is a good way of ensuring type safety in React without using Typescript.

How is it presented to the user?

Now onto how the form will be presented to the user, change the contents of your app.js file to the code below

import React, { useState } from "react";
import Accordion from "./Accordion";
import Modal from "./components/modal";
import { dataStore } from "./reduxStore";

const App = () => {
    let d = dataStore.getState();
    const [show, setShow] = useState(false);
    const [data, setData] = useState(d);

    dataStore.subscribe(() => {
        setData(dataStore.getState());
    });

    function addEntryForm() {
        setShow(!show);
    };
    
    return (
        <div>
            <h1> React Accordion/Redux Store Demo</h1>
            <div className="accordion">
                <div className="addFormBtnContainer">
                    <button className="button" onClose={addEntryForm} onClick={addEntryForm}>Add Entry</button>
                </div>
                
                <Modal show={show} onClose={addEntryForm} />
                <hr />
                <div>
                    {data.map(({title, content}) => (
                        <Accordion id={title} title={title} content={content} />
                    ))}
                </div>
            </div>
        </div>
    )
};

export default App;

From the code above this line

<Modal show={show} onClose={addEntryForm} />

Determines whether or not the modal is shown. You see show is a local variable that we are managing by state.

const [show, setShow] = useState(false);

This means that anytime, the value of the show variable changes it will be reflected in the UI, which in this case would be that our modal (or popup) is shown or hidden. The addEntryForm method simply inverses the current state i.e. the boolean show variable.

As you can see the show variable which is a boolean determines whether or not the state is shown. You can think of it this way, the modal is always in the DOM and the state of it determines whether it is display: none or display: block (or flex). However, once the user adds a new note, how is that reflected in the new note?

How is the list of notes updated?

Ok for this one, be prepared as it is a bit of a drawn out process but it will bring together all the code seen above. So when we add a note, remember the handleSubmit in the modal.js which dispatches an Add event with a payload of the the note added

handleSubmit = (e) => {
    const newContent = {
        title: e.target[0].value,
        content: e.target[1].value
    };
    dataStore.dispatch({
        type: "ADD",
        payload: newContent
    });
}

Now this event will trigger the actionReducer in the reduxStore which will save (persist) it to localStorage. Below are a few lines from that code to help you understand this

const accordionReducer = (state = [], action) => {
    
    if(action.type = "ADD" && action.payload) {
        state = [];
        if(localStorage.getItem("data")) {
            state = JSON.parse(localStorage.getItem("data"));
        }
        state.push(action.payload);
        localStorage.setItem('data', JSON.stringify(state));
    }
}

Once a new value is pushed in the state variable, all the the components that have subscribed to this store will be notified with the new state. In our app.js we have subscribed to the store as such we will be notified as and when the state changes.

    
    dataStore.subscribe(() => {
        setData(dataStore.getState());
    });

If you recall the state in our datastore is the list of objects for the notes displayed to the user. This happens by mapping each note object in (note) data to an html element. Therefore, when the data is updated with the new values, the list of notes displayed to the user is automatically updated to show the new data. Since we are monitoring it’s state via React hooks.

let d = dataStore.getState(); 
const [data, setData] = useState(d);

Summary

I have tried to make it as clear as I possibly can, however this post touches one too many topics and the code shown here is a bit non-linear. Non-linear, a more polite way of saying it is all over the place. Hence, I thought it readers would benefit by having a look at working sample code for everything discussed here. You can find the full working source code in this Github repo.

https://github.com/cptdanko/reactReduxCRUDTutorial

Conclusion

As I have said before in my previous posts, React can be quite fun to use and let’s you see the fruits of your labour earlier than some other frameworks. I hope to have captured the essence of what makes a fully functioning react app with this post. My goal with this post was to aim to teach aspects such as forms, data entry, modals and state management with react. The key thing to remember is that with react, you are exporting individual components e.g. <MyComponent />. When you use the component, you can bind values and events to it by specifying it in curly braces e.g. <MyComponent name={this.name} />. In MyComponent one way to get the value of name would be by destructing the props element e.g. const { name } = props; . One of the ways to ensure, that data is synchronised across all areas of the app is by using React Redux toolkit for statement.

React is light and fun. I hope my on/off journey with react ends this time and I spend a good couple of months working with React.

If you find any of my posts useful and want to support me, you can buy me a coffee 🙂

https://www.buymeacoffee.com/bhumansoni

Or you can  buying or even try one of my apps on the App Store.  

Get updates?


Or have a look at some useful Javascript tutorials below
https://mydaytodo.com/category/javascript/

Some of my other posts on react

Covid Case Numbers React App

In this post, I will talk about building a simple react app, for guessing the score. What score? I am glad you asked. Here in Sydney, Australia we have been in lockdown for the last 10+ weeks where we have limitations of not going anywhere beyond 5kms of where we live. We are sort of like prisoners and given our history, this is kind of ironic isn’t it? We are pretty bored here and listening to our ineffective politicians announce the daily Covid case numbers at 11am is a part of our daily routine. Over the last 10 weeks of this lockdown routine, the case numbers have become like a scorecard for a sporting event. I mean, it’s like waiting to hear the score for a game (of test cricket?) being played overseas. The daily case numbers no longer have that shock factor. My friends and I play a little guessing game where we cast our predictions for the case numbers before 11am. Yes we are all bored!!! Since I am an Software engineer I thought I should build something for fun, I mean I am not doing much else in my free time. I thought about building a “Guess the score” react app or “guess covid case numbers” react app that you can play with a group of friends. You can read more about this in the post below,

Building a React app for a Java backend (React vs Angular)

Angular is a framework that I have the most experience with, and while I still love it, I cannot deny react’s popularity. Therefore, I thought it would be a good time to explore react and maybe try to understand what makes it so special. In this post, I will talk about building a react app for Java backend e.g. to show news articles that it gets from a Java spring-boot powered REST API.


0 Comments

Leave a Reply

Avatar placeholder