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!