React - Tidy Up, and Finish


In this final video in the React section, and to wrap up this course, I am going to improve the user experience of our React CRUD application by adding in a few nice features. These are:

  • A working 'create' button :)
  • Redirection on create
  • A working 'edit' button
  • Redirection on edit

Though all of these sound quite simple, as you will soon see, they aren't quite as obvious as you might initially think.

Adding a Create Button

Let's start with the easiest thing to fix.

I'm going to add this directly to the Table component, but you may wish to rethink this implementation if you need to re-use this Table for more than just blog posts.

// /src/components/Table.js

import React, { Component } from 'react';
import { Link } from 'react-router';

export default class Table extends Component {

    // * snip *

    render() {
        return (
            <div>
                <table className="table table-hover table-responsive">
                    <!-- * snip * -->
                </table>

                <Link to="/posts/create" className="btn btn-lg btn-success">Create</Link>
            </div>
        );

React Router comes with a component for rendering our Router-friendly <a href>'s.

We need to import it:

import { Link } from 'react-router';

Then using it is really quite self explanatory:

<Link to="/posts/create" className="btn btn-lg btn-success">Create</Link>

The bit I don't like?

We link to a path. That path may very well change. If it does, we need to find / replace all instances of the path. Not the end of the world, but named routes are nicer, if not so visually as obvious. Swings and roundabouts, I suppose.

Also, remember to use className instead of class when adding in CSS classes.

Still, very easy to add these in.

Redirection On Create

We now have a lovely green button that allows us to add blog posts without manually visiting the /posts/create route.

However, when you click this button and then submit the resulting form, nothing actually happens.

Well, it does - in the background the data is POST'ed to the API and the blog post is created. But from the front end / user perspective, it doesn't look like anything happened.

Adding in a redirect is a bit of a palava. But let's do it:

Firstly, we need to wrap our Create component with the withRouter component.

By wrapping our component withRouter, we gain access to this.props.router from our component, which means we can redirect via React Router:

// /src/App.js

import React, { Component } from 'react';
import { Router, browserHistory, Route, IndexRedirect, withRouter } from 'react-router'
import List from './containers/blogPosts/list';
import Create from './containers/blogPosts/create';
import Update from './containers/blogPosts/update';
import NotFoundPage from './components/NotFoundPage';

export default class App extends Component {

  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/">
            <IndexRedirect to="/posts"/>
        </Route>
        <Route path="/posts" component={List}/>
        <Route path="/posts/create" component={withRouter(Create)}/>
        <Route path="/posts/update/:postId" component={withRouter(Update)}/>
        <Route path="*" component={NotFoundPage}/>
      </Router>
    );
  }
}

We will need to redirect both on Create, and on Update, so I have wrapped both components withRouter.

Once the component is wrapped, we can then immediately gain access to the router:

// /src/containers/blogPosts/create.js

import React, { Component } from 'react';
import { withRouter } from 'react-router';
import Form from '../../components/form';
import { createBlogPost } from '../../actions/blogPostActions';

export default class Create extends Component {

    handleSubmit(data) {
        createBlogPost(data);
        .then(res => {
            this.props.router.push("/").bind(this);
        });
    }

    render() {
        return (
            <div>
                <Form onSubmit={this.handleSubmit.bind(this)}></Form>
            </div>
        );
    }
}

As our createBlogPost function is going to return a promise, we can hook into the then-able and redirect if successful. Pretty neat.

Repeating this process for Update is easy enough:

// /src/containers/update.js

    handleSubmit(data) {
        updateBlogPost(this.props.params.postId, data)
        .then(res => {
            this.props.router.push('/');
        });
    },

We don't yet have a working 'Edit' button though, so let's fix that also.

Working 'Edit' Button

We need to pass in the current post.id to the 'Edit' button link in order to ensure we end up on the right blog post when clicking the button.

Fortunately, we are already inside the 'loop' thanks to our map function:

// /src/components/Table.js

    render() {
        return (
            <div>
                // * snip *

                {this.state.blogPosts && this.state.blogPosts.map((post, i) => {

                    return (
                        <tr key={post.id}>
                            <td>{ post.id }</td>
                            <td>{ post.title }</td>
                            <td>
                                <Link to={`/posts/update/${post.id}`} className="btn btn-default btn-sm">Edit</Link>
                                <btn onClick={() => this.deleteHandler(i)} className="btn btn-danger btn-sm">Delete</btn>
                            </td>
                        </tr>
                    );

                })}

The Link syntax is the same as previously.

However, this time we can make use of ES6's template string syntax to save us from having to use 'this' + ' ' + 'that' sort of syntax.

If you'd like to have a play around with this, I strongly recommend ES6 Katas, and here is the direct link for the Template Strings : Basics kata. A really nice way to improve your ES6 is to do one of these Katas every single day until you can do them without thinking.

Wrapping Up

This concludes the Twig, API, React, and Angular CRUD series.

I hope you have found this course useful, and I hope it has inspired you to try out the various frameworks with Symfony 3 as your back end / API. It is a really powerful combination, in my opinion.

One of the nicest parts of using Symfony as your back end is you can take full advantage of all your PHP skills, utilising Behat, PHPSpec, and all the nice things that Symfony provides (forms and validation, for example) to make a highly robust business system.

I plan to provide most of my content in this format moving forwards - as in, Symfony as the back end, and then how to use the functionality from the front end via React, Angular, or similar.

See you in the next series!

Code For This Video

Get the code for this video.

Episodes