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
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>
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>
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)
<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
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)
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}
}
-
removeItem
export const removeItem = (item) => {
return { type : 'REMOVE_ITEM', item: item}
}
-
increaseCount
export const increaseCount = (item) => {
return { type: 'INCREASE_COUNT_OF_ITEM', item: item}
}
-
decreaseCount
export const decreaseCount = (item) => {
return { type: 'DECREASE_COUNT_OF_ITEM', item: item}
}
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
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"
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"
},
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)