DEV Community

Cover image for React & Redux | A Quick Side Project
A.J. Romaniello
A.J. Romaniello

Posted on

React & Redux | A Quick Side Project

Tracker

As I am currently learning React and Redux I wanted to mess around with these new frameworks and get used to hosting them on GitHub for future projects.

This is a very basic application that showcases DOM manipulation, via React, and state management through Redux. What it allows us to do is add items to a list, we can increase the count on each item or decrease them, and remove them from the master list.

You can check out the hosted application on github pages here, or view the source code here.


The Development Process

As the development process goes, it is always a good idea to have something written down in what you expect your application to end up working like. I came up with the following:

// Our ReactDOM.render method in index.js
<Provider store={store}>
  <App/>
</Provider>

// App Component
<React.Fragment>
   <Header/> // Where we should be able to add items to the list
   <Body items={this.props.items}/> // Pass our store of props
</React.Fragment>
// Connected via react-redux to pass the props from our main index.js
Enter fullscreen mode Exit fullscreen mode

Setting Up Containers

These are simply for separating our components into a logical manner as well as increasing maintainability for future changes to components and the layout of our application.

Header

This is where we want our input form to be, so we are going to wrap this section in a div giving it a unique ID for styling/DOM manipulation.

Setting up our render method should like something like this:

<div className='add-bar'>
    <AddBar />
</div>
Enter fullscreen mode Exit fullscreen mode
Body

This container should be responsible for passing the props from our main application state down to our <ItemList/> component, and rendering this in a reasonable fashion.

Setting up our render our

<div id='item-list'>
   <ItemList items={items}/>
</div>
Enter fullscreen mode Exit fullscreen mode

Now that we have our containers set up we can go ahead and begin building our our components.


Setting Up Components

As we declared above we need at least two components: <AddBar/> and <ItemList/>, and it would make sense for <ItemList/> to house many components such as <Item/>.

So we need to declare three components <AddBar/>, <ItemList/>, and <Item/>. Which all need to be connected to our main application state.


<AddBar/>

This component should be responsible for taking user input, and adding it to our main application states list of items.

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addItem } from "../actions/item";
import '../style/form.css'
// Material UI Components & Style
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button'

class AddBar extends Component {

    constructor(props) {
        super(props);
        this.state = {
            item: '',
            count: 0
        }
    }

    handleChange = e => {
        if (this.state.item.length > 25 && e.target.value.length > 25) return
        this.setState({ item: e.target.value })
    }

    handleSubmit = e => {
        e.preventDefault()
        // Passed to props via connect method for dispatch actions/item.js
        this.props.addItem(this.state)
        this.setState({ item: '' })
    }

    render() {
        return (
            <form className="add-form" onSubmit={this.handleSubmit}>
                <TextField type='text' id='item' required={true}
                           autoComplete='off'
                           label='Item Name' value={this.state.item}
                           placeholder='Add Your Item Here' onChange={this.handleChange}
                />
                <Button type='Submit'>Submit</Button>
            </form>
        )
    }
}

// Pull main application state to props
// As well ass pull the addItem action to our props
export default connect(props => ({ item: props.item }), { addItem })(AddBar)
Enter fullscreen mode Exit fullscreen mode

<ItemList/>

This component should be responsible for passing our state and breaking it down and rendering them into specific <Item/> components.

Our component should be declared something like:


import React from 'react'
import Item from './item'

const ItemList = ({ items }) => {

    function renderItems() {
        return items.map((e, idx) => <Item key={idx} id={idx} item={e.item} count={e.count}/>)
    }

    return (
        <div className='item-list'>
             {renderItems()}
        </div>
    )
}

export default ItemList
Enter fullscreen mode Exit fullscreen mode

Notice this doesn't have to use the react-redux connect method, because we are passing our main application state items as a prop to the <Body/> container.

This also allows us to render our individual items.


<Item/>

This component should be responsible for rendering specific items and their count, as well as handling buttons for incrementing and decreasing count and removal button for removing the item from our main application state and DOM.

Setting up our class would look something like:

import React, { Component, lazy, Suspense } from 'react'
import { connect } from 'react-redux'
import '../style/item.css'
import { removeItem, increaseCount, decreaseCount } from '../actions/item'
import Card from '@material-ui/core/Card'
import Button from '@material-ui/core/Button'
import Typography from '@material-ui/core/Typography'
import ButtonGroup from '@material-ui/core/ButtonGroup'

class Item extends Component {

    handleRemove = e => {
        this.props.removeItem(this.props)
    }

    handleIncrease = e => {
        this.props.increaseCount(this.props)
    }

    handleDecrease = e => {
        this.props.decreaseCount(this.props)
    }

    render() {
        return (
             <Card className='item-card' id={this.props.id}>
                 <Typography variant='' className='item-title'>{this.props.item}</Typography>
                 <Typography variant='subtitle1' className='clicker'>{this.props.count} times</Typography>
                 <ButtonGroup className='action-row'>
                     <Button onClick={this.handleIncrease} className='item-button'>+</Button>
                     <Button onClick={this.handleDecrease} className='item-button'>-</Button>
                     <Button onClick={this.handleRemove} className='item-button'>Remove</Button>
                 </ButtonGroup>
             </Card>
        )
    }
}

export default connect(props => ({ ...props }),
    { removeItem, increaseCount, decreaseCount })(Item)
Enter fullscreen mode Exit fullscreen mode

Notice that we are pulling in three actions: removeItem, increaseCount, and decreaseCount.

Setting Up Actions

Since this was a very simple application we only needed on action file and four total methods. All that should take an item as a parameter.

  • addItem
export const addItem = (item) => {
    return { type: 'ADD_ITEM', item: item}
}
Enter fullscreen mode Exit fullscreen mode
  • removeItem
export const removeItem = (item) => {
    return { type : 'REMOVE_ITEM', item: item}
}
Enter fullscreen mode Exit fullscreen mode
  • increaseCount
export const increaseCount = (item) => {
    return { type: 'INCREASE_COUNT_OF_ITEM', item: item}
}
Enter fullscreen mode Exit fullscreen mode
  • decreaseCount
export const decreaseCount = (item) => {
    return { type: 'DECREASE_COUNT_OF_ITEM', item: item}
}
Enter fullscreen mode Exit fullscreen mode

Setting Up a Reducer

In order for us to handle these actions we need a reducer function to manipulate the applications state.

This will usually be called manage<ClassName> so we will call our file manageItems.js. Our function should take two parameters, the applications state (list of items), and an action object.

Leaving us with a new file that looks like:


function manageItems(state = { items: [] }, action) {
    switch (action.type) {
        case 'ADD_ITEM':
            action.item.id = state.items.length
            return { ...state, items: [...state.items, action.item] }
        case 'REMOVE_ITEM':
            let items = [...state.items]
            items.splice(action.item.id, 1)
            return { ...state, items: items }
        case 'INCREASE_COUNT_OF_ITEM':
            let arr = [...state.items]
            const x = arr[action.item.id]
            x.count += 1
            return { ...state, items: arr }
        case 'DECREASE_COUNT_OF_ITEM':
            let dec = [...state.items]
            const y = dec[action.item.id]
            if (y.count === 0) return state

            y.count -= 1
            return { ...state, items: dec}
        default:
            return state;
    }
}

export default manageItems
Enter fullscreen mode Exit fullscreen mode

This connects the functionality between our Item component and our main application state by allowing us to call actions to our reducer from the Item component buttons.

We set up a default parameter for our initial state, which is being called when we use createStore in our index.js file. This gets overridden with our current application state every subsequent call to this method.


Hosting to GitHub Pages

I wanted free hosting for this application, and as there are a couple of free options out there (Netlify, Heroku, etc), my codebase was already hosted on a GitHub repository.

Might as well just host it from the same location!

Installing gh-pages

There is a very useful npm package for achieving this extremely quickly, read more on it here.

All I had to do was run npm install gh-pages, add a

"homepage": "https://aj-rom.github.io/tracker"
Enter fullscreen mode Exit fullscreen mode


key and value to my package.json.

Next thing to add were the special scripts provided by gh-pages, to our package.json for us to be able to deploy this to github.

Our scripts section should now look like:

  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build",
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
Enter fullscreen mode Exit fullscreen mode

To deploy our application we run npm run predeploy which creates a /build directory with our production site. Navigate into the /build directory and open up index.html make sure everything is working the way you want and once you are satisfied go ahead and deploy it using npm run deploy.

This creates a new branch, gh-pages, and pushes only the contents of the /build folder to this branch. GitHub then automatically will detect that there is a branch that should be used for GitHub pages and your application will be up and running!


Optimizations via Lighthouse

Now that I got everything up and running I wanted to make sure everything was highly optimized and see how I could increase performance for any react-redux application in the future.

My initial page report for desktop was flawless, 100s across the board, but mobile was drastically lower performance wise.

This lead me to code splitting with React.lazy and Suspense. Which increased performance on mobile to be on par with the desktop report.

Feel free to run your own diagnostics check by navigating to the website aj-rom.github.io/tracker. Open the chrome-developer console and navigate to the Lighthouse section.

Conclusion

I learned a lot while making this application when it came to separating concerns within a React-Redux application, and how to optimize component loading using code splitting.

Top comments (0)