Simple React with Redux app

Let me first start by saying that I am in the process of getting better acquainted with how to use React, Redux, Thunk and Firestore together to create a project that I have long thought about working on, but just never really found the framework that I wanted to use. So this is hopefully the beginning of that project and I can finally see it to its full application.

Seeing that just about every beginner project uses the ToDo List in one form or another, I thought it would be fun to use a different themed project to start this off. In this article I will look at the start of what will become a much larger and more involved project that I sincerely hope you will follow along in the journey. Let’s not use the ToDo as a start, let’s create a Physical Records Tracking system.

Why a physical record tracking system

I have worked in the Records Department of a larger law firm for almost a decade deploying a Records Retention program and was one of the major decision makers in testing and purchasing the software we would use to track our physical as well as our electronic records created by attorneys for our clients. I am currently involved in an internationally know association for Records Mangers as well as a local Records Roundtable group.

In our discussions, we have worked with various major software vendors and consultants. But all this time I was wondering what the smaller law firms were using, and what did their retention programs look like. I saw a need, for a cost efficient system for law firms to get started in properly tracking their client assets. Smaller firms do not have the capital to spend thousands of dollars to purchase and maintain a system that they can do adequately using simple Excel Worksheets. While not the best option, Excel or 3×5 cards can work very well for small amounts of records. So why not create a solution that can get them one step closer to preparing to become a larger law firm, and stay within budget.

What are we tracking

For this article, I want to keep the topic in the simplest of ideas so as to not step into more difficult concepts of React too early. We are going to implement a basic data set that we can add and delete records. Please note, that the feature to delete a record should only be available to an admin and with very good cause. However, I wanted to show that it is possible to delete from a simple application perspective just like in the other ToDo articles. The option to delete will be removed in future versions of this project, or at least protected to an admin only level. But let’s not complicate this at this time.

Each record will consist of the following data points:

  • id: unique id for each record item
  • name: short name for the record
  • type: what type of record
    • Document
    • Folder
    • Expandable
  • description: further details of the item or a list of contents
  • location: where the record is stored
  • retention: Retention Bucket per Retention Policy
    • Transient: Keep till end of case
    • Client: Return to client at end of case
    • 10 Year: Keep 10 years after end of case
    • Permanent: Keep till life of Client, Property or Firm

Functionality of this app

Keeping this very simple. We will use Redux to create a data store, which will be initialized with a few starting record items. The app will allow the addition of new record items. Each record item will have a Delete button to allow removal of the item. Any new data will NOT be persisted after shutdown or restart of the app, as that topic will be for a later article working with Google Firestore to store data and manage logins.

We will use React Router to handle page transitions, as we will want a list page to display the full list of records, a page to enter new records, and a page to show details of a single record with the delete button.

The listing page will only show the record id, name and type and clicking on a record will display the detail page for that record. The detail page will display all the data for one record in a card format. The add record page will be a form with all the fields to enter. Note: to keep this simple, we will let the user create the unique id. When we move further towards using Google Firestore, we will let the database automatically generate unique ids, so for now we trust that the user will ensure they are unique.

We will use Bootstrap for styling of the Navbar and detail Cards.

Project setup

Setup may be a bit intimidating at first, but the tools for React are very easy to use once you get some practice. You will need to be familiar with using the Command Line, and have a good beginning grasp of HTML, CSS, and especially JavaScript. There are many good beginner tutorials out there, so you may consider visiting one of them. For example, I refer to W3Schools often. You must already have NodeJS installed on your computer. I will be using VisualStudio Code as my text editor of choice, but you can use any simple or more complex text / code editor you prefer.

Create a working folder on your computer where you want to store your React projects, and then open a terminal (Command Line) window at that folder and type the following command

npx create-react-app record-tracker

The npx command will look online for and run the create-react-app nodejs installer which will use what follows as the project name. This will create a new subfolder that will house all of the data needed for your application. When the installer finishes, the instructions tell you to cd into the new directory and run the startup script. This will startup a local web server on your computer that will watch for any updates to the files in your app and automatically rebuild and host your changes in a web browser at http://localhost:3000/

Enter the following commands in your terminal window

cd record-tracker
code .
npm run start

Be sure to stay in the record-tracker folder in your terminal window when entering any further npm commands below, otherwise you will not be installing the proper libraries in the correct location. npm usually warns you that you are not in a project folder otherwise.

Note the space and period after code. This tells VisualStudio to open and use the current directory as the workspace folder. Please read the VisualStudio guide on how to use the editor. If you choose to use a different editor, then skip the command “code .

In the new directory of files, the portion we are concerned with is the ‘src‘ folder. This is where we will create the files we need for the project. The first step is to do a little house cleaning to remove code we do not need. Edit the src/App.js file to look like the following:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <h1>Records Tracker</h1>
    </div>
  );
}

export default App;

Next, we need to add the Bootstrap CSS code to help with styling, edit the file public/index.html and add the following just above the </head> tag

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

And to we should add the Bootstrap JS code to assist in any special Bootstrap functionality. Add the following to the public/index.html file just above the </body> tag

<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

We will be using the React Router, Redux and React-Redux libraries in this project, so we need to add them to our project. In a terminal window, enter the following:

npm install react-router-dom
npm install redux react-redux

The react-router-dom will help with seamless page navigation, and the redux and react-redux libraries will add support for easy data management.

Create placeholder components

We need to create the plain React component so that we can have pages to traverse to with the Route and Navbar component. The will be functional components until we need to convert any of them to class components.

Create a subfolder called components under the src folder ( src/components/ ). This is where we will store all our components. For this simple app, we will keep all our components in this folder, but when the app expands to a larger project, we will consider creating additional subfolders to organize components by their functional purposes.

In the src/components folder, create the following blank files

  • src/components/Navbar.js
  • src/components/RecordsList.js
  • src/components/RecordDetail.js
  • src/components/AddNewRecord.js

For each of the files add the following base code:

Navbar.js

import React from 'react'

const Navbar = () => {
    return (
        <div>Navigation bar here</div>
    )
}

export default Navbar

RecordList.js

import React from 'react'

const RecordList = () => {
    return (
        <div>Record listing here</div>
    )
}

export default RecordList

RecordDetail.js

import React from 'react'

const RecordDetail = () => {
    return (
        <div>Record detail here</div>
    )
}

export default RecordDetail

AddNewRecord.js

import React, { Component } from 'react'

class AddNewRecord extends Component {
    render() {
        return (
            <div>New Record form here</div>
        )
    }
}

export default AddNewRecord

You will notice that all of these are just about exactly the same to start with, they have just enough information to create a very plain React component. The only one that is different is the AddNewRecord component. We will need this to be a class style component because later in the article we will need to track state of the form elements and the data that a user enters into the form. Only class components can track state, not functional components.

In each of these files, first we import the React library. Next we create a constant using the same name as the filename to hold the component. The constant is set to equal a function, and the function we are using what is called an arrow function ( please review the ES6 JavaScript specs to understand arrow functions ). Inside the function we must return some JSX. JSX is JavaScript XML which is very much like HTML. We can add any HTML templating in this return statement. For now, I am passing in a single DIV with some generic text. It is also very important that we only have one outermost HTML element. We can have as many child HTML elements inside, as long as the full lot is surrounded by one outermost HTML element. Then finally, we export the constant as the default object that can be imported into other JavaScript files by that name.

Setup the Routes

We will setup the routes for our main App component. Edit src/App.js to now look like the following:

import React from 'react';
import './App.css';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import RecordList from './components/RecordList';
import RecordDetail from './components/RecordDetail';
import AddNewRecord from './components/AddNewRecord';

function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <h1>Records Tracker</h1>
        <Switch>
          <Route exact path='/' component={RecordList} />
          <Route path='/detail/:id' component={RecordDetail} />
          <Route path='/add' component={AddNewRecord} />
        </Switch>
      </div>
    </BrowserRouter>
  );
}

export default App;

There are three components we need from the ‘react-router-dom’ library. BrowserRouter must wrap the outermost component for your app in order to manage the urls and history for the web browser and web server. The we use the <Switch> component that will look at all of the child <Route> components for a match to a path. We use ‘exact path‘ for the root path, because otherwise the root path would also match all other paths. Note the path for the RecordDetail, it has a parameter for ‘id‘ at the end, marked by using a colon in front. The value that is put in the URL of a web browser will be passed into the params that can be retrieved by our app. So for example, if we had a record with the id of ‘3‘, we could use a url of ‘localhost:3000/detail/3‘ in order to find the details of a Record item. We will see more about this later. For each Route component, we pass which component that should be rendered when a URL is matched.

We can now test our routes. In your web browser, enter the following URL patterns to see each of the base components we created.

  • To see the RecordList – http://localhost:3000/
  • To see a RecordDetail – http://localhost:3000/detail/3
  • To see the AddNewRecord – http://localhost:3000/add

Create the Navigation Bar

Next, let’s add a basic navigation bar that will be displayed on all the pages. In the src/components/Navbar.js file, edit so the file now looks like the following:

import React from 'react'
import { NavLink } from 'react-router-dom'

const Navbar = () => {
    return (
        <nav className="navbar navbar-expand-lg navbar-dark bg-primary">
            <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                <span className="navbar-toggler-icon"></span>
            </button>
            <div className="collapse navbar-collapse" id="navbarNavAltMarkup">
                <div className="navbar-nav">
                    <NavLink exact to='/' className="nav-item nav-link">Full List</NavLink>
                    <NavLink to='/add' className="nav-item nav-link">Add New Record</NavLink>
                </div>
            </div>
        </nav>
    )
}

export default Navbar

We import NavLink from ‘react-router-dom‘ to handle the links, which will automatically add an ‘active‘ class to a link that is the current page which the Bootstrap CSS will use to style a link appropriately. The <nav> HTML and child elements are taken from the Bootstrap styling for a navigation bar and then we add a couple NavLink tags in place of the usual <a> tags. Note also, that when using React JSX, we must change the ‘class‘ tag property name to ‘className‘ because the word class is a reserved word in JavaScript.

Next, in src/App.js, we need to add an import with the other imports in order to bring the <NavBar /> component into our App:

import Navbar from './components/Navbar'

Then, just above our <h1>Records Tracker</h1> element, add the <Navbar /> component in src/App.js like below:

...
<BrowserRouter>
      <div className="App">
        <Navbar />
        <h1>Records Tracker</h1>
...

Now you can refresh the webpage if not already automatically refreshed and you will see our new navigation bar that takes us between the Full Records listing and to the page what will soon be our Add New Record form.

Redux and our data

Many beginning tutorials would next work on laying out the components first, in order to help explain how data is passed around in React. Seeing that there are plenty of beginning articles on this topic, the best of which is from ReactJS, I think it makes sense that we can work out the data portion of our project seeing that we have a pretty good understanding on how our project will flow from the notes above.

In a larger application, you will end up with multiple Redux Actions and Reducer files that would be organized under ‘actions‘ and ‘reducers‘ folders, but seeing that this is a simple project to start, I will instead create one folder to house the data related files under a folder called ‘logic‘. Create the following folder: ‘src/logic‘, and then create the file ‘src/logic/actions.js‘ and add the following code:

export const addNewRecord = (newRecord) => {
    return {
        type: 'ADD_RECORD',
        record: newRecord
    }
}

export const removeRecord = (recordId) => {
    return {
        type: 'REMOVE_RECORD',
        recordId
    }
}

We will have two actions that affect our data, adding a new record and deleting a record. The action.js file is not required, as you can instead place the action objects directly into the dispatch function (to be seen later), but it is far easier to read and manage your files if separated like this. Each action is just a function that returns an object that will be passed to our Reducer function through a dispatch call from the Redux Store. Each action must have a ‘type‘ associated with it, which is best named by the action being performed. We will be passing into these functions the data that we need. For the ADD_RECORD action, we will pass in the full new Record object that will be created in the AddNewRecord component. In the REMOVE_RECORD action we will pass only the data we need to remove a Record object, and that is the unique ID of a Record (this is why we need to be sure we use unique IDs for each record item, which we will enforce in a much later article that deals with Firestore)

Next, create the Reducer function file called: ‘src/logic/rootReducer.js‘ and add the following code:

const initState = {
    records: [
        { id: 'builderb001', name: 'Bond application', type: 'document', description: 'Fully executed bond application, signed by client', location: 'shelf01', retention: 'Permanent' },
        { id: 'builderb002', name: 'Client docs', type: 'folder', description: 'Client documents provided during first meeting', location: 'shelf01', retention: 'Client' },
        { id: 'builderb003', name: 'Bob Builder Bond', type: 'expandable', description: 'Full case file for Bob Builder', location: 'shelf01', retention: 'Transient' }
    ]
}

const rootReducer = (state = initState, action) => {
    switch (action.type) {
        case 'ADD_RECORD':
            return {
                ...state,
                records: [...state.records, action.record]
            }
        case 'REMOVE_RECORD':
            const newState = state.records.filter(record => {
                return record.id !== action.recordId
            })
            return { ...state, records: newState }
        default:
            return state
    }
}

export default rootReducer

Since we are not using a live database, i think it is very useful to have some sample data available when the application first starts up. We will initialize the startup data by creating the ‘initState‘ constant and pass in an object that is an array of record objects. I added three sample bits of data with appropriate variable names we will reference in the RecordDetail component later.

When we construct the ‘rootReducer‘ constant, we will pass in this initial state if one does not already exist in the Redux store, as well as the action which was the object we created in the ‘actions.js‘ file. Inside our rootReducer arrow function, we check to see which action.type was passed into the function from actions.js

If we are receiving the ADD_RECORD action, we will return a new object that is comprised of the current Redux store state, and then add in the new Record object into the ‘records‘ field in the state. As part of the rules for using Redux, we are not allowed to change the state directly, so we must create new objects based upon that original state with any changes. We first use the spread operator on the current state ( …state ) to lay out each portion of the state array, and then separate what we want to add into that state array by a comma. What we want to add in, is in effect replace what the whole of the ‘records’ object in that array with our new set of records. To create this new records object, we first spread the existing set of objects belonging to the records item ( …state.records ), and after the comma add in a new record that was passed in with the action ( action.record ).

I could have shortcut the return object a little because we actually only have one item in our state ( records ), but just in case there were other items ( which will happen later ), i choose to add the new record in what seems like a two step process so that we do not overwrite other variables that may be in state

For the REMOVE_RECORD action, we just cycle through the existing state by filtering the records and keeping all items except any that match the action.recordId we pass in. When the comparison is true in the filter, we keep that record item, but when it is false ( we have a match to IDs because of the not equal to operator ) then we do not keep that item. We then pass in this filtered list into a new state and return that object to be stored in the Redux store.

Next we must tell Redux how to implement the store and where the data is located. in src/index.js edit so that it now looks like the following:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './logic/rootReducer';

const store = createStore(rootReducer)

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));

serviceWorker.unregister();

From the default version of the src/index.js file, we have added three import statements for createStore, Provider and our rootReducer. We then create a constant for ‘store’ by setting equal to the function that creates the store, and in that function we pass in our rootReducer. This will link our Reducer and Actions to the Redux store. We then wrap the original <App /> component with the Provider component and pass our store into that component. The Provider component will manage the access to the Redux store for us, and provide that access to any child elements inside the Provider (which is our App and all it’s components)

Finish Records Listing

Now that we have a data store, let’s start building the working components. To create the Records Listing we need to make some adjustments to the src/components/RecordList.js component. Change the contents to match the following:

import React from 'react'
import { connect } from 'react-redux'
import { useHistory } from 'react-router-dom'

const RecordList = ({ records }) => {
    let history = useHistory()

    const handleClick = (e) => {
        history.push('/detail/' + e.target.id)
    }
    
    const recordsList = records.map((record) => {
        return (
            <button key={record.id} id={record.id} onClick={handleClick} className='list-group-item list-group-item-action list-group-item-light'>
                {record.id}: {record.name}
            </button>
        )
    })
    return (
        <div className='list-group container text-left'>{recordsList}</div>
    )
}

const mapStateToProps = (state) => {
    return {
        records: state.records
    }
}

export default connect(mapStateToProps)(RecordList)

To explain, I will break this up in two concepts for what was added. We need to connect to the store so we import the connect component from react-redux. Then if you look down at the bottom of the file to the export you will see that we used a Higher Order Component (HOC) called connect that is passed in the records object from our store state using a function called mapStateToProps(). In the mapStateToProps function just above the export, we receive the full Redux Store state in the function, and return only the portion we need that will be automatically added to our RecordList component in its props field. You can see we are grabbing the records out of the props in the RecordList function at the top utilizing a concept called Object Destructuring, which is easier and quicker than using something similar to the following:

const RecordList = () => {
    const records = this.props.records

Inside the component, and just before we render our JSX, we create an object called recordsList that is equal to mapping through each record in our records object and returning a <button> for each item. In that button we have to have a key as part of any list in React which we set to the hopefully unique record.id and we also set the HTML id of the button to the same record.id so we can reference it in the onClick method we call. We then can render this mapped list in our return() function using Reacts expression embedding.

The second concept is how we handle letting a user click on one of the items and redirect to the detail page for that item. The button onClick method we called handleClick will receive the information about the button via the e.target object and since we included a unique HTML id attribute for each button we can retrieve vie e.target.id. We use the useHistory component that we imported at the top of the file which is from react-router-dom and will allow us to push in a new location to force the user to go to. This location will be in the form of http://localhost:3000/detail/:id like we setup in the <Route>s in src/App.js

If you now take a look at our app in the browser, you should see a list of three items, and clicking any of them will take you to the page we will use for showing the details from the next section.

Showing the Record Details

Setting up the record details is actually fairly simple, because we will again connect to our Redux store to get our data, but in the mapStateToProps function, we will grab only the one record we need by using the URL pattern that is provided to us as part of the props sent from the Route component that matched in src/App.js (<Route path=’/detail/:id’>). From the existing props object, which we can get as the second argument in the mapStateToProps function, there is a ‘match‘ object, which has a ‘params‘ object and inside there is the ‘id‘ portion of the URL match generated from the <Route> component.

We then grab the record from props using Object Destructuring in our function defining the RecordDetail component:

const RecordDetail = ({ record }) => {

Once we have the record passed into our object, we then layout a simple form HTML block using some Bootstrap styling and with JSX expressions to display our data. We will use a very similar form in the next section to create our AddNewRecord component. In a later series, I think it will make sense to create a new component that can display this form but also allow editing or adding new records, but for now we will keep it simple and duplicate some code.

Adjust the src/components/RecordDetail.js component to now look like the following:

import React from 'react'
import { connect } from 'react-redux'

const RecordDetail = ({ record }) => {
    return (
        <form className='container'>
            <div className='form-group row'>
                <label htmlFor='recordId' className='col-sm-2 col-form-label text-right'>Record Id:</label>
                <div className='col-sm-10'>
                    <input type='text' readOnly className='form-control-plaintext' id='recordId' value={record.id} />
                </div>
            </div>
            <div className='form-group row'>
                <label htmlFor='name' className='col-sm-2 col-form-label text-right'>Name:</label>
                <div className='col-sm-10'>
                    <input type='text' readOnly className='form-control-plaintext' id='name' value={record.name} />
                </div>
            </div>
            <div className='form-group row'>
                <label htmlFor='type' className='col-sm-2 col-form-label text-right'>Type:</label>
                <div className='col-sm-10'>
                    <input type='text' readOnly className='form-control-plaintext' id='type' value={record.type} />
                </div>
            </div>
            <div className='form-group row'>
                <label htmlFor='description' className='col-sm-2 col-form-label text-right'>Description:</label>
                <div className='col-sm-10'>
                    <textarea readOnly className='form-control-plaintext' id='description' rows={3}>{record.description}</textarea>
                </div>
            </div>
            <div className='form-group row'>
                <label htmlFor='location' className='col-sm-2 col-form-label text-right'>Location:</label>
                <div className='col-sm-10'>
                    <input type='text' readOnly className='form-control-plaintext' id='location' value={record.location} />
                </div>
            </div>
            <div className='form-group row'>
                <label htmlFor='retention' className='col-sm-2 col-form-label text-right'>Retention:</label>
                <div className='col-sm-10'>
                    <input type='text' readOnly className='form-control-plaintext' id='retention' value={record.retention} />
                </div>
            </div>
        </form>
    )
}
const mapStateToProps = (state, ownProps) => {
    let id = ownProps.match.params.id
    return {
        record: state.records.find(record => record.id === id)
    }
}

export default connect(mapStateToProps)(RecordDetail)

You now should have the ability to click a record in the full list and see the detail form page. Click on the full list link in the navbar to return to the listing

Adding a new Record

Adding a new record is also fairly simple. Adjust the src/components/AddNewRecord.js file to look like the following and then we will discuss:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addNewRecord } from '../logic/actions'

class AddNewRecord extends Component {
    state = {
        record: {
            id: '',
            name: '',
            type: '',
            description: '',
            location: '',
            retention: ''
        }
    }

    handleChange = (e) => {
        this.setState({
            [e.target.id]: e.target.value
        })
    }

    handleSubmit = (e) => {
        e.preventDefault()
        this.props.addRecord(this.state)
        this.props.history.push('/')
    }

    render() {
        return (
            <form className='container' onSubmit={this.handleSubmit} >
                <div className='form-group row'>
                    <label htmlFor='recordId' className='col-sm-2 col-form-label text-right'>Record Id:</label>
                    <div className='col-sm-10'>
                        <input type='text' className='form-control' id='id' onChange={this.handleChange} />
                    </div>
                </div>
                <div className='form-group row'>
                    <label htmlFor='name' className='col-sm-2 col-form-label text-right'>Name:</label>
                    <div className='col-sm-10'>
                        <input type='text' className='form-control' id='name' onChange={this.handleChange} />
                    </div>
                </div>
                <div className='form-group row'>
                    <label htmlFor='type' className='col-sm-2 col-form-label text-right'>Type:</label>
                    <div className='col-sm-10'>
                        <input type='text' className='form-control' id='type' onChange={this.handleChange} />
                    </div>
                </div>
                <div className='form-group row'>
                    <label htmlFor='description' className='col-sm-2 col-form-label text-right'>Description:</label>
                    <div className='col-sm-10'>
                        <textarea className='form-control' id='description' rows={3} onChange={this.handleChange} ></textarea>
                    </div>
                </div>
                <div className='form-group row'>
                    <label htmlFor='location' className='col-sm-2 col-form-label text-right'>Location:</label>
                    <div className='col-sm-10'>
                        <input type='text' className='form-control' id='location' onChange={this.handleChange} />
                    </div>
                </div>
                <div className='form-group row'>
                    <label htmlFor='retention' className='col-sm-2 col-form-label text-right'>Retention:</label>
                    <div className='col-sm-10'>
                        <input type='text' className='form-control' id='retention' onChange={this.handleChange} />
                    </div>
                </div>
                <div className='form-group row'>
                    <div className='col-sm-10 offset-sm-2'>
                        <button className='btn btn-success'>Add Record</button>
                    </div>
                </div>
            </form>
        )
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        addRecord: (record) => dispatch(addNewRecord(record))
    }
}

export default connect(null, mapDispatchToProps)(AddNewRecord)

This time, the component is setup as a class component because we need to keep track the state of the form entry fields until the form is submitted. Please note now, that this is not a good example of how to enforce data integrity, as we are not verifying all the fields are input, or if we have unique record id information. We are also not cleaning the data from potential security risks, so please do not use this form for any production use. This is just a simple example to get you thinking.

We initialize the state as empty strings. Next you see a function for handling any changes in our fields below. As long as we give each field the same ‘id‘ name as the field names in our data set we can pass this state object directly to our function that handles the submit.

The handleSubmit function prevents the default behavior of a form so that we can control what happens, otherwise the page will refresh and we will loose our data. We pass the state to a function that we add to the props of our component called ‘addRecord‘. You will find this function down at the bottom of our file in a special function called mapDispatchToProps. We then force the application to go to the home page where we will again see the full list of records, including our new record.

The mapDispatchToProps is a way that we can add the callback dispatch function that points to the object in our Redux Actions file, which connects to the Redux Store and adds the new data in our rootReducer file. Note, that this time around when we wrap our export constant with the connect component we use a ‘null‘ value for the first value, and then provide our mapDispatchToProps function. This is because the first option for connect component must be a mapStateToProps function, and since we do not need to get any Redux store state, we pass in a null function. The second argument will be our dispatch function. Note, do not confuse the Redux store state with the component state. The Redux state is in our Provider component in src/index.js

The <form> is mostly the same as our form in the RecordDetail component, except for a button to allow us to tell the form to use the onSubmit function call, and the added onChange attributes to our <input>s to handle changing the component state to store our form data

Now it mostly works 🙂

For the most part, this is a decent example for a simple React and Redux application. The article turned out a little bit longer than I expected at first, and I will need to reread and test all the above again to see how I might be able to explain better. Your comments, confusions, or ideas are welcome below. I have only created a couple simple React apps at this point in time, so I am continuing to learn better ways to improve this app.

Now you can run this app, clicking Full List in the navbar will get you all of the records you have added. Clicking on a record takes you to the detail page for that record, and you can add a new record. Don’t be surprised that you can add records without any values, and you can easily create records with duplicate record id’s. Remember, this is only an example and should not be used in production.

Next chance I get, I will try to improve upon the form entry validation. I also plan to connect this to a Google Firebase app so I can add login functionality and storing the data (so we can remove the fake initial dataset in rootReducer.js – initState – and use our persisted data)

Thanks, and I hope this helps in some form or another to give you ideas for your own projects.

Add a Comment

Your email address will not be published. Required fields are marked *