DEV Community

aurel kurtula
aurel kurtula

Posted on • Edited on

Creating an app with react and firebase - part one

After having explored the basics of firebase and react, I thought I'd use them all together in this tutorial. In this three part series, I am going to create another todo app. I'm going to use react, the basics of which I covered here where I made a simpler version of the same app. I'm also going to use react routing, which I also covered in this post.

Since I don't want this tutorial to be very long, I'm going to add firebase to this project in part two. In that tutorial, we'll move the data from our react component state to the firebase database. Then in part three we'll add authentication where users can add their own private todo items.

Create the static markup

First we'll quickly create the basic design of the app. Everything I'll do here I have already covered else where. Let's start by installing the package we need for routing in react.

yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode

The App component is going to be the main component. It will hold the state and the logic of the application. However, let's start by creating the basic structure. If you want to start in codesandbox that means start editing in index.js. If you create a react application through the terminal, you start in src/App.

import React, {Component} from 'react' 
import { BrowserRouter, Route, Link } from 'react-router-dom';
import './App.css'
class App extends Component {
  state = {
    items: {
      1123: {
        item: 'item one',
        completed: false
      },
      2564321: {
        item: 'item two',
        completed: true
      }
    }
  }
  render() {
    return (
      <BrowserRouter>  
        <div className="wrap">
          <h2>A simple todo app</h2>
          <ul className="menu">
            <li><Link to={'/'}>To do</Link></li>
            <li><Link to={'/completed'}>Completed</Link></li>
          </ul>
          <Route exact path="/" render={props => {
              let lis = []
                for(let i in this.state.items){
                  if(this.state.items[i].completed === false){
                    lis.push(<li key={i}>{this.state.items[i].item} <span >x</span></li>)
                  }
                }
                return(<ul className="items"> { lis } </ul>  )
              }   
            }  />
          <Route exact path="/completed" render={props => {
              let lis = []
                for(let i in this.state.items){
                  if(this.state.items[i].completed === true){
                    lis.push(<li key={i}>{this.state.items[i].item} <span >x</span></li>)
                  }
                }
                return(<ul className="items"> { lis } </ul>  )
              }   
            }  />
        </div>
      </BrowserRouter>   
    );
  }
}
export default App;
Enter fullscreen mode Exit fullscreen mode

When loading the app in your browser, you'll be able to navigate between the homepage and /completed and see the difference.

For an explenation on how the above code works, read my previous tutorial on the basics of React Router

Using child components

Let's create a child component which will take care of the duplicate code. Create a file at components/ItemsComponent.js and add the following code.

import React from 'react'
const ItemsComponent=({items, done})=> {
    let lis = []
    let mark = done === false ? '\u2713' : 'x';
    for(let i in items){
        if(items[i].completed === done){
          lis.push(<li key={i}>{items[i].item} <span >{mark}</span></li>)
        }
    }
    return(<ul className="items"> {lis} </ul>  )
}
export default ItemsComponent;
Enter fullscreen mode Exit fullscreen mode

That is a stateless functional component, as you can see, it doesn't need a class (a shout out to @omensah for nudging me on this direction). It's perfect for cases like these, where the logic doesn't require to make use of functionality that we'd otherwise inherit from Component class. Cory House has perfectly compared the two styles in this post

Let's modify the App component to make use of ItemsComponent which will also clarify the deconstructed arguments in line 2.

import ItemsComponent from './components/ItemsComponent';
class App extends Component {
  ..
    return (
      <BrowserRouter>  
        <div className="wrap">
          ...
          <Route exact path="/"
            render={props => 
              <ItemsComponent  items={this.state.items} done={false}/> 
            }/>
          <Route exact path="/completed" 
            render={props => 
              <ItemsComponent  items={this.state.items} done={true}/> 
            }/>
        </div>
      </BrowserRouter>   
    );
  }
}
export default App;
Enter fullscreen mode Exit fullscreen mode

We render the ItemsComponent component using render rather than using the component attribute, which I covered when writing about react routers because we needed to pass it the items a boolian to signal which items to display. With that the use of ES6 deconstruction is self explanatory:

const ItemsComponent=({items, done})=> { ... }        
Enter fullscreen mode Exit fullscreen mode

The above could have otherwise be writen as

const ItemsComponent=(props)=> { ... }
Enter fullscreen mode Exit fullscreen mode

Which we would have had to then reach in the props object to retrieve items or done.

Adding actions

The first two actions that we'll work on are the ability to mark an item as complete, and also completely delete any completed item.

As I said the App component is going to be the main component. It holds our main state. So let's write the methods that modifies the state.

class App extends Component {
  state = {
    items: {
      1123: {
        item: 'item one',
        completed: false
      },
      2564321: {
        item: 'item two',
        completed: true
      }
    }
  }
  completeItem=(id)=>{
    let items =   {
        ...this.state.items, 
        [id]: {...this.state.items[id], completed: true      }
      }
    this.setState({ items })
  }
  deleteItem = (id) => {
    let  {[id]: deleted, ...items} = this.state.items;
    this.setState({ items })
  }
  ...
Enter fullscreen mode Exit fullscreen mode

completeItem method takes the items from the current state, then we select the item with the relevant id, and finally change its completed property to true.

Deleting the relevant object is slightly different. I'm currently trying to learn more about the spread operator and that's why I added it above. I found the snippet ... guess where? ... at stackoverflow

Next, completeItem and deleteItem methods need to be passed to the ItemsComponent

  render() {
    return (
      ...
          <Route exact path="/"
            render={props => 
              <ItemsComponent  
                items={this.state.items} 
                done={false}
                action={this.completeItem}
                /> 
            }/>
          <Route exact path="/completed" 
            render={props => 
              <ItemsComponent  
                items={this.state.items} 
                done={true}
                action={this.deleteItem}
                /> 
            }/>
       ...
    ) 
Enter fullscreen mode Exit fullscreen mode

Finally we just strap action to an onClick event over at components/itemsComponent.js

const ItemsComponent=({items, done, action})=> {
    let lis = []
    let mark = done === false ? '\u2713' : 'x';
    for(let i in items){
        if(items[i].completed === done){
          lis.push(<li key={i}>{items[i].item} 
            <span onClick={()=> action(i)}>{mark}</span></li>)
        }
      }
      return(<ul className="items"> {lis} </ul>  )
}
Enter fullscreen mode Exit fullscreen mode

Note, the only thing that's changed is the deconstruction of the action method in the first line. Then I added it to the span. i is the id of each object within the items object.

Adding items

A todo Application is no good if users can't add items. At the moment, the item's are hard coded, but that was to help us get to this point.

The way this will work is that I want users to be able to add new items only when thay are viewing the uncompleted items, in other words, only when they are at the root path and not the /completed path. Let's add the input box inside the components/ItemsComponent.js file:

const ItemsComponent=({items, done, action})=> {
    ...
    return (
        <div>
            {done
            ? (<ul className="items"> {lis} </ul>)
            : (
            <div>
                <form>
                    <input type="text" /> 
                </form>
                <ul className="items"> {lis} </ul>
            </div>
            )}
        </div>
    );                   
}
Enter fullscreen mode Exit fullscreen mode

Remember, done is a boolian, if true it means the items are marked as completed, hence we do not want to see the form, else, we do.

React requires the outer div to wrap the entire output, and it also requires the form and ul to be wrapped with an element.

Finally, just as with the delete and complete operations, we'll add the form's logic at App components and link it via props with the form. Let's create the logic in App.js

class App extends Component {
  ...
  addItem=(e)=> {
    e.preventDefault();
    let items =  {
      ...this.state.items, 
      [new Date().valueOf()]: {
        item: this.todoItem.value, 
        completed: false     
      }
    }
    this.setState({
      items
    });
  }
  render() {
    return ( 
        ...
          <Route exact path="/"
            render={props => 
              <ItemsComponent  
                ...
                addItem={this.addItem}
                inputRef={el => this.todoItem = el}
                /> 
            }/>
         ...   
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

addItem will execute on form submit. Then it simply adds an item to the state. new Date().valueOf() is a basic way of creating a unique id. this.todoItem.value is created from the inputRef attribute that we created in ItemsComponent. You can read more about Refs (as they are called) in the documentation

Now let's use addItem and inputRef in the form over at ItemsComponent.js.

const ItemsComponent=({items, done, action, addItem, inputRef})=> {
    ...
    return (
      ...
       <form  onSubmit={addItem}>
          <input ref={inputRef} type="text" /> 
       </form>
       <ul className="items"> {lis} </ul>
       ...
    );                   
}
Enter fullscreen mode Exit fullscreen mode

We attach the input node as a reference to inputRef (which is passed through props).

Conclusion

So far we have a basic react application where we can add items, mark them as complete then delete any that are completed. We also made use of routing to differentiate between the two.

The completed project can be found at github. I'll have a branch for each tutorial.

The next tutorial is going to connect the react state with the Firebase database.

Top comments (0)