In this fourth post of the series we add the ability to delete existing users from our CRUD based user management single page application (SPA) utilizing Laravel and React.
Series posts
Posts in this series:
- Part one - Initial setup with user accounts data table
- Part two - Adding the ability to create new users
- Part three - Adding the ability to edit existing users
- Part four - Adding the ability to delete existing users
With more to follow…
Prerequisites and assumptions
For this discussion I assume you’ve followed the instructions in the third post, and we continue by building on what we did previously.
You can also find the source code for this post here.
Create Laravel assets
To start we’ll create the Laravel assets needed to support the React components.
Routing
First let’s add the new route for the React single page application (SPA) dealing with user deletion. At the same time let’s also wrap the ability to create, edit, and delete users in middleware ensuring that only the administrator role is able to perform these actions.
We do this by implementing RoleMiddleware from the spatie/laravel-permission Laravel package around the routes we wish to protect. You can read more about it here.
Edit the routes/web.php file and add the following code:
<?php
// Default route
Route::get('/', function () {
return redirect( route('home'));
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Route::get('/get_users', 'UserController@getUsers');
// Ensure only administrators are able to add/edit/delete users
Route::group(['middleware' => ['auth', 'role:administrator']], function () {
Route::post('/create_user', 'UserController@store');
Route::put('/edit_user/{id}', 'UserController@update');
Route::delete('/delete_user/{id}', 'UserController@destroy');
});
Route::get('/users', 'UserController@index')->name('users');
We added a new route, /delete_user/{id}, which accepts a DELETE request containing the ID of the user account we wish to remove. This route will handle the actions that need to take place when the delete user form is submitted via React to the Laravel back end.
We chose to add this new route as a DELETE request, because we’d like to follow Laravel’s resource controller action standards (reference).
We’ve also wrapped the create, edit, and delete routes in middleware only allowing users with the role administrator to perform these actions.
app/Http/Kernel.php
Because we’ve wrapped the create, edit, and delete routes in spatie/laravel-permission route middleware we need to modify the app/Http/Kernel.php file as described in the documentation.
Edit the app/Http/Kernel.php file and add the make the following change:
...
protected $routeMiddleware = [
...
'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
];
This will enable us to utilize the role:administrator directive in the routes/web.php file we modified above.
app/Http/Controllers/UserController.php
Let’s update the user controller next. Edit the app/Http/Controllers/UserController.php file and replace the destroy() method with the following code:
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
// Pull the user record from the database
$user = User::findOrFail($id);
// Remove the user account from the DB
$user->delete();
// Create response to be returned to the view
$response['result']['type'] = 'success';
$response['result']['message'] = 'The user was successfully deleted!';
$response['data'] = $user->__toString();
// Return JSON response
return response()->json($response);
}
This method first attempts to pull the user record being modified from the database with a findOrFail() method call. Assuming that completes successfully we then proceed to delete the user and remove them from the database. Next, we return a JSON result to the view to confirm the selected user account was indeed removed.
That should be the end of our Laravel tasks, and from here on out the rest of our work will be creating the React javascript assets.
Develop React assets
resources/js/components/Index.js
Since we did most of the heavy lifting in a previous post the changes to the resources/js/components/Index.js file are relatively minor:
// ==> ADD THE DELETE USER MODAL CODE BLOCK @ LINE 299
{/* Delete user form modal */}
<div>
<FormModal
isOpen={ this.state.modalsOpen['delete'] }
onRequestClose={ (e) => this.toggleModal('delete', this.state.user) }
contentLabel="Delete User Confirmation"
title="Delete User Confirmation"
modalAppElement="#users"
styleOverride={ new Object({width: '40%', left: '35%', height: '45%'}) }
>
{/* Define and render the actual delete user form */}
<DeleteForm
onClose={ (e) => this.toggleModal('delete', this.state.user) }
onUpdate={ this.fetchUserData }
user={ this.state.user }
/>
</FormModal>
</div>
{/* END Delete user form modal */}
This code is almost exactly the same as the Create user form modal code block above it in the source.
The only differences are the 1) update to the label and title, and 2) we are now passing this.state.user as the second argument to the this.toggleModal method. As we’ll see in a moment this allows the delete user form to properly identify which user account we want to remove, and then pass this information to the Laravel back end.
resources/js/components/DeleteForm.js
Lastly we have the new resources/js/components/DeleteForm.js file. Due to the size of this file we’ll explore it piece-by-piece instead of pasting the whole file in as a single chunk. As always you can view the source here for a complete view of the code.
// Standard import items
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
// Formik table related imports
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import LoadingOverlay from 'react-loading-overlay';
import isEmpty from 'lodash/isEmpty'
// Our custom components
import FlashMessage from './FlashMessage';
We start by importing all the libraries and other assets we’ll need for this component.
export default class DeleteForm extends Component {
constructor(props) {
super(props);
this.state = {
// Show/hide Laravel style flash messages regarding actions
// taken on the page
showFlashMessage: false,
// Container for request response data/message/etc sent back
// by the server
requestResult: null,
// Show/hide the form overlay on ajax requests to notify
// the user activity is happening
showOverlay: false,
// Show/hide the delete confirmation form, 'cause it looks
// odd to still have it once the item is deleted
hideForm: false,
}
//Bindings
this.hideFlashMessage = this.hideFlashMessage.bind(this);
}
Next we declare the DeleteForm class, and build the class constructor. The constructor defines four properties in the state we’ll use later on in the class:
- showFlashMessage: Utilized to show/hide Laravel style flash messages regarding actions taken on the page. Based loosely off the ideas in the laracasts/flash repository.
- requestResult: We’ll store the request response data/messages/etc sent back by the back end server
- showOverlay: This property will track if we should show/hide the form overlay on ajax requests to notify the user activity is happening
- hideForm: This property will track if we should hide the delete user account confirmation text and submit button, which we’ll want to do once the select user account has been deleted
// Hide the the flash message at the top of the modal
hideFlashMessage() {
this.setState({
showFlashMessage: false
});
}
Next we throw in a quick method to toggle showing the flash message(s).
// Examine this.props and this.state and
// return class response (typical React elements)
render() {
// Using Laravel validation on the back end,
// so no need to add any properties
// to this object
const ValidationSchema = Yup.object().shape({
});
We start the render() function off by declaring the ValidationSchema Yup object. However, we don’t populate it with any properties, because we are utilizing Laravel back end data validation. If we wanted to change our minds later on and include front end validation then we could easily add items to the constant.
// Prepare and return React elements
return (
<div>
{/* Display Laravel style flash messages
in response to page actions */}
<FlashMessage
show={ this.state.showFlashMessage }
result={ this.state.requestResult }
/>
{/* END Display Laravel style flash messages
in response to page actions */}
{/* Form overlay to visually indicate
activity is occurring to the user */}
<LoadingOverlay
active={this.state.showOverlay}
spinner
text='Working...'
>
The next code block opens up the return statement, and begins to populate the items that will be rendered on the page to the user. The FlashMessage component handles displaying Laravel style flash messages based on the response from the back end server, and the LoadingOverlay allows us to indicate to the user that activity is occurring when the form is submitted. Both of these components will be described in the next section.
{/* Form block */}
<div onClick={this.hideFlashMessage}>
{/* Formik form */}
<Formik
initialValues={{
id: this.props.user.id,
}}
validationSchema={ValidationSchema}
We then add an onClick action to the form’s div that hides any visible flash messages, and then we begin building the delete user form using Formik. The first prop we set for the Formik component is the initial values of the form components. Since this is an existing user we set the id value to the ID of the user account to be deleted. We also set the front end validation rules using the empty ValidationSchema constant we created earlier.
onSubmit={(values, actions) => {
// Show the overlay while the ajax request is processing
this.setState({
showOverlay: true,
});
// Submit the request to the server and handle the response
axios.post(
'/delete_user/' + this.props.user.id,
values,
{timeout: 1000 * 10},
)
.then(response => {
if (response.data.result) {
// Store the data/message/etc
// sent back by the server in the state
this.setState({
requestResult: response.data.result,
hideForm: true,
});
// Reset the form if the user was created successfully
if (response.data.result.type == 'success') {
actions.resetForm(this.props.initialValues);
}
};
})
.catch( error => {
// Init container for flash error message
let data = {};
// Is this a Laravel back end validation error?
if (typeof error.response !== 'undefined') {
if (error.response.status == '422') {
// Render the errors on the page's form's elements
actions.setErrors(error.response.data.errors);
// Define flash message to show user
data = {
type: 'danger',
message: <p className="mb-0">
<i className="far fa-frown ml-1"></i>
Unable to complete request.
Please correct the problems below.</p>,
};
}
}
// Define flash message to show user
// if one hasn't already been set
if (_.isEmpty(data)) {
data = {
type: 'danger',
message: <p className="mb-0">
<i className="far fa-frown ml-1"></i>
Error: Unable to process your request at this time.
Please try again later.</p>,
};
}
// Pass the flash message data to the
// flash message display component
this.setState({
requestResult: data,
});
})
.then( () => {
// Hide the ajax processing overlay
this.setState({
showOverlay: false,
});
// Tell the form we are done submitting
actions.setSubmitting(false);
// Show the flash message with
// the results of the page action
this.setState((state, props) => ({
showFlashMessage: true,
}));
});
}}
>
The next code block–while long–is standard ajax request/response type code. We start by showing the overlay letting the user know we are taking action behind the scenes. Then we begin the axios DELETE request by defining the endpoint URL and collecting the form values to submit.
The first then block handles a success condition by storing the server response in the state’s requestResult property. It also hides the delete user form’s confirmation text and submit button, since it doesn’t make sense to confirm and submit the form twice.
A catch block follows, and handles one of two cases: 1) The back end server has returned validation errors, or 2) some sort of general error has occurred such as lack of network connectivity. In either case the cause of the error is saved in the state’s requestResult property which can then be displayed to the user as a flash message.
The final then block performs the post request actions by hiding the overlay, setting the form’s submit state to false, and displaying the flash message(s) to the user with the results of the form submit request (i.e. success or error).
{({ errors, dirty, status, touched, isSubmitting, setFieldValue }) => (
<Form className="mb-5" hidden={ this.state.hideForm }>
{/* Form data fields */}
<div className="col-12">
<div className="form-group text-center">
<div>
<i className="text-warning fa fa-4x fa-question-circle mb-2" />
</div>
<div className="h3">
<strong>Are you sure?</strong>
</div>
<div className="h5">
You will be unable to recover this user's account!
</div>
</div>
<Field name="id" type="hidden" />
</div>
{/* END Form data fields */}
{/* Form submit/close buttons */}
<div className="form-group ml-3 mt-4 text-center">
<button
type="submit"
className="btn btn-outline-success"
disabled={isSubmitting || !isEmpty(errors)}
>
<i className="fa fa-fw fa-plus mr-1"></i> Delete User
</button>
<button type="button" className="btn btn-outline-secondary ml-3" onClick={this.props.onClose}>
<i className="fa fa-times-circle mr-1"></i> Close
</button>
</div>
{/* END Form submit/close buttons */}
And now we are into the meat of the form. The code block above creates the form confirmation text elements, the submit button, and a hidden field with the user ID to be deleted.
</Form>
)}
{/* END {({ errors, dirty, status, touched, isSubmitting }) => ( */}
</Formik>
{/* END Formik form */}
</div>
{/* END Form block */}
</LoadingOverlay>
{/* END Form overlay to visually indicate activity is occurring to the user */}
</div>
);
}
}
The rest of the code simply closes the div and React component blocks.
The end result
To wrap up we first want to compile the code we wrote above using the following terminal command:
npm run dev
Once that’s finished browse to your Laravel application, click the Users link in the navigation bar, and then click the trashcan icon in one of the user rows. You should then see the following:
Summary
This post has covered the fourth step in creating a User management SPA utilizing Laravel and React. In the next part of the series we’ll explore refactoring, DRYing up our code, along with some polishing.
You can find the source code for this post here.
If you have any comments or questions please don’t hesitate to reach out.
Thanks!
Comments