React from scratch: Part 2

Subscribe to my newsletter and never miss my upcoming articles

This is the second part of the series "React from scratch". This time we will start creating a To-Do-Application with the setup we did in part 1!

Prerequisites

Part 2 will start where we left off in part 1. If you didn't already, go ahead and finish part 1 or just clone it from my repo and start from there.

New depdendencies

Before we start creating the To-Do-Application, we will add bootstrap as a css library to make our life easier and our routing-tools:

$ npm i bootstrap react-router react-router-dom

Next we will import bootstrap into our index.scss by adding the following line at the top of the file:

@import "~bootstrap/scss/bootstrap";

After that we create two new directories in our src-folder, one for our components and one for our containers:

$ mkdir containers
$ mkdir components

Containers & components

I like to make a clear distinction between containers and components.

  • Container are only there for displaying our UI using the functionality we provide for them. They don't implement functions.
  • Components contain the logic for our application and "decide" which containers to display at which time. They provide the functionality for the containers, by passing it to them.

Now let's start writing our first container:

import React from 'react';
import Routes from '../Routes';

const Root = () => (
    <Routes />
);

export default Root;

This containers only job is to return our application routes. These Routes will keep track of all different routes-component-pairs. Create a new file in the src-folder called Routes.js with the following content:

import React from 'react';
import { Switch, HashRouter } from 'react-router-dom';
import { Route } from 'react-router';
import TodoPage from './components/Todo';

export function Routes() {
  return (
    <HashRouter>
      <Switch>
        <Route path='/' component={TodoPage} />
      </Switch>
    </HashRouter>
  );
}

export default Routes;

As you can see we used our newly added dependencies in this function. Switch contains all our routes that we will declare in this application. Currently we only declared one route which points to this address "localhost:8000". The component TodoPage will be rendered at this address.

Next up we need to implement TodoPage. Therefore we will create a new file called Todo.js in the components folder. The file should look like this:

import React, { Component, Fragment } from 'react'
import TodoListContainer from '../containers/TodoList';

export class Todo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: [
        {
          id: 1,
          title: 'Create Part 1',
          text: 'Starting with project setup'
        },
        {
          id: 2,
          title: 'Create Part 2',
          text: 'creating the UI with components and containers'
        },
        {
          id: 3,
          title: 'Create Part 3',
          text: 'To be decided'
        }
      ],
    };
  }

  render() {
    return (
      <Fragment>
        <div className="container-fluid">          
          <TodoListContainer 
            todos={this.state.todos}
          />
        </div>
      </Fragment>
    );
  }
}

export default Todo;

In this file we create some dummy-todo's. After that we pass the todo's to our TodoListContainer that we write next. For that we need to create a file in the containers-folder called TodoList and add the following content:

import React, { Fragment } from 'react';

const TodoListContainer = ({todos}) => (
  <Fragment>
    {
      todos.map(({id, title, text}) => (
          <div className="mb-3 card" key={id}>
            <div className="card-header">
              <h5>{title}</h5>
            </div>
            <div className="card-body">
              <p className="card-text">{text}</p>
            </div>
            <div className="card-footer">
              <button className="btn btn-primary w-25">Edit</button>
              <button className="btn btn-success ml-2 w-25">Check</button>
            </div>
          </div>
        )
      )
    }
  </Fragment>
);

export default TodoListContainer;

In this container we loop over all the todo's we passed down from our Todo-component. While looping through them we create a bootstrap card for each todo, to display the title and the text. Next we change our background color to something more suitable than red. For that we open our index.scss and change the background-color as follows:

@import "~bootstrap/scss/bootstrap";

body {
  background-color: #d7dfdb;
}

Now we clean up our index.js by getting rid of the App-function and implementing our Root-container like this:

import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom';
import Root from './containers/Root';
import './index.scss';

ReactDOM.render(
  <StrictMode>
    <Root />
  </StrictMode>,
  document.querySelector('#root'),
);

Let's startup our project via npm start and look at the layout we just build. It should look like this: Todo-App-1.PNG

More functionality

Now we will add the functionality to add a new todo to our list. First thing we have to do is to setup our Todo-Component to display an add-button. Furthermore we need to implement the functionality to add a new todo to our todos-array.

For that we have to modify our Todo.js as follows:

import React, { Component, Fragment } from 'react'
import TodoListContainer from '../containers/TodoList';
import NewTodoContainer from '../containers/NewTodo';

export class Todo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showNewTodo: false,
      title: '',
      text: '',
      todos: [
        {
          id: 1,
          title: 'Create Part 1',
          text: 'Starting with project setup'
        },
        {
          id: 2,
          title: 'Create Part 2',
          text: 'creating the UI with components and containers'
        },
        {
          id: 3,
          title: 'Create Part 3',
          text: 'To be decided'
        }
      ],
    };
  }

  toggleNewTodo() {
    this.setState({
      showNewTodo: !this.state.showNewTodo
    });
  }

  onChange(event) {
    this.setState({ [event.target.name]: event.target.value });
  }

  onSubmit(event) {
    event.preventDefault();
    const { text, title } = this.state;
    this.setState({
      todos: [{ id: this.state.todos.length + 1, title, text }, ...this.state.todos],
      showNewTodo: false,
      title: '',
      text: '',
    });
  }

  render() {
    const { showNewTodo } = this.state;
    return (
      <Fragment>
        <div className="container-fluid">
          <div className="col w-25 mt-4 mx-auto">
            { showNewTodo ? 
              (<Fragment>
                <button className="mb-2 w-100 btn btn-danger" onClick={this.toggleNewTodo.bind(this)}>Cancel</button> 
                <NewTodoContainer 
                  onChange={this.onChange.bind(this)}
                  onSubmit={this.onSubmit.bind(this)} />
              </Fragment>)
              : (<button className="mb-2 w-100 btn btn-success" onClick={this.toggleNewTodo.bind(this)}>Add Todo</button>)
              }
            <TodoListContainer 
              todos={this.state.todos}
            />
          </div>
        </div>
      </Fragment>
    );
  }
}

export default Todo;

First of all I added three new properties to our state.

showNewTodo is a boolean that helps us to toggle between the add-button and the add new todo form.

title and text will contain the title and text for the new todo.

Next up I added two new function. onChange writes the title and text of our new todo into the state.

onSubmit creates a new todo-object and adds it to the front of our todos array.

And last but not least we added the necessary HTML to show the add-button or the NewTodoContainer depending on our showNewTodo boolean.

Now we just need to create a new file in the containers-folder called NewTodo.js and add the following content:

import React from 'react';

const NewTodoContainer = ({onChange, onSubmit}) => (
  <form className="mb-4">
    <fieldset>
      <legend>New Todo</legend>
    </fieldset>
    <div className="form-group">
      <label htmlFor="title">Title</label>
      <input type="text" name="title" className="form-control" id="title" placeholder="Enter title" onChange={onChange} />
    </div>
    <div className="form-group">
      <label htmlFor="text">Text</label>
      <input type="text" id="text" name="text" className="form-control" placeholder="Enter text..." onChange={onChange} />
    </div>
    <button type="submit" className="btn btn-primary" onClick={onSubmit}>Submit</button>
  </form>
);

export default NewTodoContainer;

This is just a simple form with two input fields, that both implement our onChange-function and a submit-button that implements our onSubmit-function. Now that this is done, we can restart our development server by hitting npm start.

You should see this:

Todo-App-2.PNG

And after hitting the "Add Todo"-button it should look like this:

Todo-App-3.PNG

Conclusion

That's it for part 2 of this series. We implemented some basic routing and a component that handles everything regarding our todos. Furthermore we implemented some UI-Elements like a add-todo-form and a list of all todos. I hope you enjoyed it! For the next part we will finally be tackling redux and statemanagement in react and adding some more features to our application.

All the code for this multipart series can be found in this GitHub-Repository

No Comments Yet