Build a Notes App with React, React Router, Redux, and Redux Form

Zoran Šaško
Oct 12 · 8 min read
Image for post
Image for post

Before we start with the coding, let us remind ourselves what is React. React is a JavaScript library for building user interfaces. It allows us to create reactive interfaces very easily because it can listen for state change and when state change occurs, it will update UI layout. It’s component based and it uses XML-like syntax called JSX.

In this article we are going to build simple Notes App that will use NestJS backend. In order to start backend app please download it’s source code and check it’s README.md:

At the end of this article you can find source code of React application that we are going to build in this article.

Let’s start!

Firstly, we are going to initialize React project by invoking the following npm command:

And from ‘src’ folder remove all the files. We’ll put our classes there, so we don’t need pregenerated ones.

In generated index.html (located inside of ‘public’ folder’) please add link to bootstrap.css file inside of ‘head’ tag:

And at the very bottom of ‘body’ tag put the following JavaScript libraries that will allow us to have bootstrap style of application:

We can now add all required libraries in ‘dependencies’ section of ‘package.json’:

And install them by invoking the following command:

Our app classes (inside of ‘src’ folder) will be the structured by using the following folders:

  • actions: Contains all actions that user can make
  • apis: A place for storing different API configurations (in our case only one api config)
  • components: Containing all the components that will be used in building of our app, like Header, LoadingSpinner etc.
  • reducers: Containing all reducers

Let us start by creating ‘history.js’ file that will return method for handling of React browser history:

This method will be used in different places, in different classes of the app and it will be used for routing to different routes.

Creating actions that user can make we can start by specifying which action types user can start. So let’s create ‘actions/types.js’ file with the following action types:

After we have specified action types, we can resume on writing the different actions methods (in ‘actions/index.js’ file):

In every action method we are doing some async request via ‘notes’ api. Notes API class is basically preconfigured axios object with base url pointing to our backend. After we made some request, like POST request to our backend, we are dispathing the result to the reducer by invoking ‘dispatch’ command with type of action and with response data as ‘payload’. In some methods like ‘createNote’ after dispatching the response from the backend, we are using session history object in order to redirect user to main screen (route ‘/’).

In the ‘apis/notes.js’ file we are exporting note api, which is basically axios object with preconfigured base url:

The reason why we want to create separate note api file is because of simplicity of updating base url if it’s need to be changed.

The next step after creating actions is to create reducers that are reducing response fetched via actions into a state, that can be read from our components.

Let us start by writing the code of ‘reducers/noteReducer.js’:

In our reducer we are handling different types of actions and their payload. Data is stored in state in ‘key: value’ format, where the ‘key’ is the ID of note and ‘value’ is note object itself. We are using lodash library to make mappings (in ‘FETCH_NOTES’) and removal (in ‘DELETE_NOTE’) easier.

After we’ve created notes reducer, let us create combine our notes reducer with form reducer (‘reducer/index.js’):

Redux form is used to manage form state in Redux. Redux Form Reducer is used for updating the Redux store based on the changes that are coming from the application. Later in the article we are going to use ‘reduxForm’ and ‘Field’ components that will allows us the mentioned functionalities work seamlesly.

So far, we have created actions, reducers, api and now we can resume on creating components. Firstly we are going to create ‘index.js’ where we are going to create Redux store and we are going to render our main App component into the DOM.

Method ‘createStore’ creates a Redux store that holds the complete state tree of the app. It takes reducers that are returning the next state tree and it also takes Redux Thunk middleware. Redux Thunk middleware allows us to write action creators that return a function instead of an action.

The ‘Provider’ makes Redux store available to all nested components because it’s rendered on the top level of the app.

In the listing above you can see that we are also using Redux DevTools Extension which will allow us to see the Redux state. We can also see the actions with payload that are executed when user for example saves or updates a note.

Image for post
Image for post

First component that we are going to create is App.js (in folder ‘components’):

Don’t be afraid of a lot of import statements, on the top of this class. We are going to create all those components later in this article.

It also contains React Router, the component that will allow us to navigate to different ‘pages’, i.e. it will be responsible for rendering different components (like ‘NoteCreate’ component, when user navigates to the ‘/note/new’ url).

As we can see in App component, we are rendering ‘Header’ component and afterwards different components based on current url. The ‘Switch’ is used for rendering the first ‘Route’ that matches it’s location attribute. Some ‘Route’ components have ‘exact’ property and this is used in order not to invoke similar ‘Route’ with very similar location attribute.

Before we start writing our own components, let us remind ourselves which types of components exist in React apps:

  • Functional — it’s just a function that accepts props and returns React element
  • Class — a class that requires you to extend from React.Component and to create a ‘render’ method in order to return React element

The main difference between functional and class components is that you can’t use state and lifecycle hooks in functional components.

The second component that we are going to create is ‘Header’ component, which is functional component (since it’s not class and doesn’t extends from ‘React.Component’).

The ‘Header’ component renders simple header for our application.

In our app, we are going use simple progress indicator, so we are going to create appropriate functional component:

First page that user will see when the app is loaded will display a list of notes. Component that will be responsible for rendering a list of notes is ‘notes/NoteLists.js’ component:

NotesList is a class component and it loads data via ‘this.props.fetchNotes’ method called inside of ‘componentDidMount’ lifecycle method. The lifecyle method ‘componentDidMount’ runs after the component output has been rendered to the DOM.

Method ‘render’ is class-based component method for rendering a content. Inside of this method we are invoking appropriate methods for displaying a list of notes in case where there are notes available (method ‘renderList’) or appropriate message, in case when notes list is empty (method ‘renderEmptyListMessage’).

Method ‘renderList’ displays a list of messages, by going through all notes and by rendering appropriate list content with note data. ‘Link’ component is used for creating a link to appropriate endpoint by using React Router.

Method ‘renderEmptyListMessage’ displays a message in a case when there is no notes available.

On the bottom of component, we can find ‘connect’ wrapper function that is responsible for connecting a React component to a Redux store. By ‘mapStoreToProps’ we are subscribed to Redux store updates. That means any time store is updated, ‘mapStoreToProps’ will be called. The result of ‘mapStateToProps’, a plain object, will be merged to component props. The second argument of ‘connect’ function are action creators (in our case ‘fetchNotes’ action) that can be invoked via props object of our component.

Very nice, another component that we are going to create is ‘notes/NoteShow.js’:

In this component we are invoking ‘fetchNote’ method with appropriate note ID, taken from url, when component is mounted. In method ‘mapStateToProps’ we are taking note from Redux store, that we have fetched via note api. In ‘connect’ wraper function we are loading two action creators: ‘fetchNote’ and ‘deleteNote’ that will be responsible for invoking a note fetch or delete functionality.

Because the form for editing and creating new note will contain the same field, we are going to create generic component that will be used in both component for editing and creating note and it will notify parent component when it’s submitted:

In ‘render’ method we are rendering a form with appropriate elements inside of it. Here we can see ‘Field’ component that is Redux Form component that connects the input component with the Redux Form logic. Method ‘renderInput’ is a method that returns rendered input fields. It also contains prop with meta information that will be used for validation.

Method ‘renderError’ checks if error message should be displayed, based on if user has accessed the field and didn’t specified any value.

In form, you can see that ‘onSubmit’ has assigned ‘handleSubmit’ handler with ‘submit’ handler inside of it. You can think of the redux-form ‘handleSubmit’ as a middle layer for your our submit handler that will be accessible in our parent component.

Method ‘onSubmit’ sends the form values to the parent component (that we are going to create next).

Validate constant is used by Redux Form to check weather some field has failed validation, and if returns not empty list, it means that form has failed a validation.

On the bottom of file, we can see wrapper function ‘reduxForm’ that allows us to use redux form fields and validation to work.

Finally we are now going to create a component responsible for creating new note:

Because we are using ‘NoteForm’ component, we are assigning its prop ‘onSubmit’ to the same named function inside of ‘NoteForm’ to handle invoking of action creator ‘createNote’ with form values.

On the bottom we can see ‘connect’ wrapper function without ‘mapStateToProps’ assigned, but with assigned ‘createNote’, since we are only to use this action creator in context of this component. So that’s all the code for component that’s responsible for creating a new note. Neat! 😃

Edit note component will be slightly bigger than component for creating a new note, since it need to load existing note data form DB:

In ‘render’ method we are checking if note data is loaded and if it isn’t, we are displaying appropriate loading spinner (‘LoadingSpinner’ component). If note data is loaded from state, we are assigning it’s data to ‘NoteForm’ component via ‘initialValues’ prop. We are using lodash library method called ‘pick’ in order to take only few attributes from note object that are used in form (for example, a note object will also contain a ‘ID’ property, but it’s not used in ‘NoteForm’ so we are not picking it). On the prop ‘onSubmit’ we are assigning method with the same name which invokes ‘editNote’ action with appropriate note data.

In ‘componentDidMount’ we are fetching a note based on note ID.

Function ‘mapStateToProps’ is used for taking an note from store, afterwards the note fetching request was finished.

On the bottom of file we have a ‘connect’ wrapper method that besides of mapping state to props contains a mapper for action creators like ‘fetchNote’ and ‘editNote’.

Very nice. Congratulations, you have finished the app and you can start it by invoking the following command:

Btw, make sure that backend server is also running. 😃

Source code of sample app built in this article:

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store