Laravel React CRUD Tutorial Part 2


Posted 5 years ago by Ryan Dhungel

Category: CRUD React Laravel

Viewed 10544 times

Estimated read time: 11 minutes

This is continuation of the previous tutorial "Laravel React CRUD Tutorial". Previously we implemented Create, Read and Delete feature. This time lets implement Update feature. Implementing update is bit more work because we need to use routing. So we will be using react-router-dom, which is the most popular routing package for react.

So the idea here is that, once we click on edit or update button. We will be taken to different component. There will not be a page reload or anything like that. It is basically a SPA(Single Page Application). So lets begin!

Laravel backend edit and update methods

Previously we created only index(), create() and destroy() mehtods in our TaskController. Lets create edit() and update() as well. This is how TaskController should look like now.

<?php

namespace App\Http\Controllers;

use App\Task;
use Illuminate\Http\Request;

class TaskController extends Controller {
	// apply auth middleware so only authenticated users have access
	public function __construct() {
		$this->middleware('auth');
	}

	public function index(Request $request, Task $task) {
		// get all the tasks based on current user id
		$allTasks = $task->whereIn('user_id', $request->user())->with('user');
		$tasks = $allTasks->orderBy('created_at', 'desc')->take(10)->get();
		// return json response
		return response()->json([
			'tasks' => $tasks,
		]);
	}

	public function create() {
		//
	}

	public function store(Request $request) {
		// validate
		$this->validate($request, [
			'name' => 'required|max:255',
		]);
		// create a new task based on user tasks relationship
		$task = $request->user()->tasks()->create([
			'name' => $request->name,
		]);
		// return task with user object
		return response()->json($task->with('user')->find($task->id));
	}

	public function show($id) {
		//
	}

	public function edit($id) {
		$task = Task::findOrFail($id);
		return response()->json([
			'task' => $task,
		]);
	}

	public function update(Request $request, $id) {
		// update task
		$input = $request->all();
		$task = Task::findOrFail($id);
		$task->update($input);
		return response()->json($task->with('user')->find($task->id));
	}

	public function destroy($id) {
		Task::findOrFail($id)->delete();
	}
}

Back to React frontend

Lets begin by installing react-router-dom package for routing.

// install react-router-dom
npm install react-router-dom

resources/assets/js/index.js

// import components from the package
import { BrowserRouter, Switch, Route } from 'react-router-dom';

Wrap the entire application(components) in BrowserRouter component. Inside that use Switch component to wrap the components. So far we have only one but we will be creating another component responsoble for editing task. So lets import here, then we will move on to creating it.

This is how your resources/assets/js/index.js file should look like.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// import App component
import App from './components/App';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import TaskEdit from './components/TaskEdit';

// change the getElementId from example to app
// render App component instead of Example
if (document.getElementById('root')) {
    ReactDOM.render(
        <BrowserRouter>
            <div>
                <Switch>
                    <Route path="/:id/edit" component={TaskEdit} exact={true} />
                    <App />
                </Switch>
            </div>
        </BrowserRouter>,
        document.getElementById('root')
    );
}

App.js

// import on top
import { Link } from 'react-router-dom';

Then inside reanderTasks() method, together with Delete button, add a link using react-router-dom's Link component. It works like regular <a href=">Link</a>.

<Link className="btn btn-sm btn-success" to={`/${task.id}/edit`}>
    Edit
</Link>

Here is the full code for App.js. I have added some code to show user name and task created_at date as well.

// App.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Link } from 'react-router-dom';

export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            name: '',
            tasks: []
        };
        // bind
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.renderTasks = this.renderTasks.bind(this);
        this.handleDelete = this.handleDelete.bind(this);
        this.handleUpdate = this.handleUpdate.bind(this);
    }
    // handle change
    handleChange(e) {
        this.setState({
            name: e.target.value
        });
        // console.log('onChange', this.state.name);
    }
    // create handleSubmit method right after handleChange method
    handleSubmit(e) {
        // stop browser's default behaviour of reloading on form submit
        e.preventDefault();
        axios
            .post('/tasks', {
                name: this.state.name
            })
            .then(response => {
                console.log('from handle submit', response);
                // set state
                this.setState({
                    tasks: [response.data, ...this.state.tasks]
                });
                // then clear the value of textarea
                this.setState({
                    name: ''
                });
            });
    }
    // render tasks
    renderTasks() {
        return this.state.tasks.map(task => (
            <div key={task.id} className="media">
                <div className="media-body">
                    <div>
                        {task.name}{' '}
                        <span className="text-muted">
                            {' '}
                            <br />by {task.user.name} |{' '}
                            {task.updated_at
                                .split(' ')
                                .slice(1)
                                .join(' ')}
                        </span>
                        <div className="btn-group float-right">
                            <Link className="btn btn-sm btn-success" to={`/${task.id}/edit`}>
                                Edit
                            </Link>
                            <button onClick={() => this.handleDelete(task.id)} className="btn btn-sm btn-warning">
                                Delete
                            </button>
                        </div>
                    </div>
                    <hr />
                </div>
            </div>
        ));
    }
    // get all tasks from backend
    getTasks() {
        axios.get('/tasks').then((
            response // console.log(response.data.tasks)
        ) =>
            this.setState({
                tasks: [...response.data.tasks]
            })
        );
    }
    // lifecycle method
    componentWillMount() {
        this.getTasks();
    }
    // handle delete
    handleDelete(id) {
        // remove from local state
        const isNotId = task => task.id !== id;
        const updatedTasks = this.state.tasks.filter(isNotId);
        this.setState({ tasks: updatedTasks });
        // make delete request to the backend
        axios.delete(`/tasks/${id}`);
    }
    // handle update
    handleUpdate(id) {
        axios.put(`/tasks/${id}`).then(response => {
            this.getTasks();
        });
    }

    render() {
        return (
            <div className="container">
                <div className="row justify-content-center">
                    <div className="col-md-8">
                        <div className="card">
                            <div className="card-header">Create Task</div>
                            <div className="card-body">
                                <form onSubmit={this.handleSubmit}>
                                    <div className="form-group">
                                        <textarea
                                            onChange={this.handleChange}
                                            value={this.state.name}
                                            className="form-control"
                                            rows="5"
                                            maxLength="255"
                                            placeholder="Create a new task"
                                            required
                                        />
                                    </div>
                                    <button type="submit" className="btn btn-primary">
                                        Create Task
                                    </button>
                                </form>
                                <br />
                                {this.renderTasks()}
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

Now let's go ahead and create EditTask.js file inside resources/assets/js/components

It is going to look similar to App.js file. The difference is that we dont need to render all tasks. we dont need to delete task as well.

Beside that its pretty much similar. The major difference is that the task id (the one we want to edit) will be passed down to this EditTask component via react router dom package's Link component. Because this package is wrapping entire components of our application in it's BrowserRouter componentn in index.js right?

Because of that, we can get the task id (the one that is clicked to edit) using props like so:

// just an example
// you can console.log inside render method to the outcome
// this will give you the task id once clicked in App.js
this.props.match.params.id

Ok , here is the complete code for TaskEdit.js. This code should be self explanatory. Its similar to what we did in App.js. We are using axios put method to send update request to laravel backend. The url to make update request is /tasks/id

Once we send update request and get the response, we switch back to the App.js using history.push('/') method. 

// TaskEdit.js

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

class TaskEdit extends Component {
    constructor(props) {
        super(props);
        this.state = {
            name: '',
            task: ''
        };
        // bind
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    // handle change
    handleChange(e) {
        this.setState({
            name: e.target.value
        });
        // console.log('onChange', this.state.name);
    }
    // create handleSubmit method right after handleChange method
    handleSubmit(e) {
        // stop browser's default behaviour of reloading on form submit
        e.preventDefault();
        axios
            .put(`/tasks/${this.props.match.params.id}`, {
                name: this.state.name
            })
            .then(response => {
                console.log('successfully edited the task');
                this.props.history.push('/');
            });
    }
    // get all tasks from backend
    getTasks() {
        axios.get(`/tasks/${this.props.match.params.id}/edit`).then((
            response // console.log(response.data.tasks)
        ) =>
            this.setState({
                task: response.data.task,
                name: response.data.task.name
            })
        );
    }
    // lifecycle method
    componentWillMount() {
        this.getTasks();
    }

    render() {
        console.log(this.props.match.params.id);
        return (
            <div className="container">
                <div className="row justify-content-center">
                    <div className="col-md-8">
                        <div className="card">
                            <div className="card-header">Edit Task</div>
                            <div className="card-body">
                                <form onSubmit={this.handleSubmit}>
                                    <div className="form-group">
                                        <textarea
                                            onChange={this.handleChange}
                                            value={this.state.name}
                                            className="form-control"
                                            rows="5"
                                            maxLength="255"
                                            required
                                        />
                                    </div>
                                    <button type="submit" className="btn btn-primary">
                                        Edit Task
                                    </button>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

export default TaskEdit;

That's it. You have build an awesome CRUD app with Laravel and React. This is pretty awesome right?

If you have any questions or suggestions. Feel free to leave your comment below. Cheers!