This content originally appeared on Alligator.io and was authored by Alligator.io
This article will be covering React hooks, introduced in version 16.8 and the Context API, introduced in 16.3 and mixing them together to build a fully functional CRUD application. Here we won’t be using any external API calls but we’ll get our hands dirty with hard-coded objects which will serve as the state.
The introduction of the Context API solves one major problem: prop drilling. The process of getting our data from one component to another through layers of nested deep components. Whereas Hooks helps us, React developers, by allowing us to use a functional rather than class-based components. Where we needed to utilize a lifecycle method, we had to use a class-based approach. And we now no longer have to call super(props) or worry about binding methods or the this
keyword.
Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.
-- John Carmack. Oculus VR CTO
Less talk more code. Let’s start coding ???
As you’ll notice, we’ll be making use of Tailwind CSS for the styling of our app.
Firstly we’ll start with setting up our React project using Create React App with the following command:
$ npx create-react-app react-contextAPI
We’ll now initialize our package.json
file with yarn init
- and make sure we have the following dependencies below:
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.0",
"eslint": "^6.8.0",
"tailwindcss": "^1.2.0"
To add react-router-dom
and Tailwind CSS as dependencies, just run the following command:
$ npm i react-router-dom tailwindcss
And now here are some components we’ll create in a components
directory:
- Home.js
- AddEmployees.js
- EditEmployees.js
- EmployeeList.js
- Heading.js
Import these main components inside your App
component. We’ll also have to import Route
and Switch
from react-router-dom
. And beforehand we will wrap our app with GlobalProvider
which we need from GlobalState
(which we’ll define later).
Here's a quick intro to React Router if this is all new to you.
Our App.js
file will look something like this:
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import './stylesheet/styles.css';
import { Home } from './components/Home';
import { AddEmployee } from './components/Addemployee';
import { EditEmployee } from './components/Editemployee';
import { GlobalProvider } from './context/GlobalState';
function App() {
return (
<GlobalProvider>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/add" component={Addemployee} exact />
<Route path="/edit/:id" component={Editemployee} exact />
</Switch>
</GlobalProvider>
);
}
export default App;
We will now move forward with printing the list of Employees inside our EmployeeList.js file. The className
s that you’ll notice are Tailwind CSS utility classes and they help style our app.
EmployeeList.js
import React, { Fragment, useContext } from "react";
import { GlobalContext } from "../context/GlobalState";
import { Link } from "react-router-dom";
export const Employeelist = () => {
const { employees, removeEmployee, editEmployee } = useContext(GlobalContext);
return (
<Fragment>
{employees.length > 0 ? (
<Fragment>
{employees.map(employee => (
<div
className="flex items-center bg-gray-100 mb-10 shadow"
key={employee.id}
>
<div className="flex-auto text-left px-4 py-2 m-2">
<p className="text-gray-900 leading-none">{employee.name}</p>
<p className="text-gray-600">{employee.designation}</p>
<span className="inline-block text-sm font-semibold mt-1">
{employee.location}
</span>
</div>
<div className="flex-auto text-right px-4 py-2 m-2">
<Link to={`/edit/${employee.id}`}>
<button
onClick={() => editEmployee(employee.id)}
className="bg-gray-300 hover:bg-gray-400 text-gray-800 font-semibold mr-3 py-2 px-4 rounded-full inline-flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-edit"
>
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
</Link>
<button
onClick={() => removeEmployee(employee.id)}
className="block bg-gray-300 hover:bg-gray-400 text-gray-800 font-semibold py-2 px-4 rounded-full inline-flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-trash-2"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</div>
</div>
))}
</Fragment>
) : (
<p className="text-center bg-gray-100 text-gray-500 py-5">No data</p>
)}
</Fragment>
);
};
In the above code we imported GlobalState
and useContext, one of the built-in React Hooks, giving functional components easy access to our context.
Moreover, we imported our employees
object, removeEmployee
and editEmployees from our GlobalState.js
file.
Let’s move on creating a GlobalState file where we will make our function inside of which w’ll dispatch our action.
import React, { createContext, useReducer } from "react";
import AppReducer from "./AppReducer";
const initialState = {
employees: [
{
id: 1,
name: "Ishan Manandhar",
location: "Kathmandu",
designation: "Frontend Developer"
}
]
};
export const GlobalContext = createContext(initialState);
export const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(AppReducer, initialState);
function removeEmployee(id) {
dispatch({
type: "REMOVE_EMPLOYEE",
payload: id
});
}
function addEmployee(employees) {
dispatch({
type: "ADD_EMPLOYEES",
payload: employees
});
}
function editEmployee(employees) {
dispatch({
type: "EDIT_EMPLOYEE",
payload: employees
});
}
return (
<GlobalContext.Provider
value={{
employees: state.employees,
removeEmployee,
addEmployee,
editEmployee
}}
>
{children}
</GlobalContext.Provider>
);
};
We added some functionality to dispatch an action which goes into our reducer file to switch upon the case that corresponds to each action.
We also defined the initial state of our employee array with hard-coded values inside the object. Along with the dispatch type we will also add what payload it receives. Let’s move on to our AppReducer
file and write some switch cases for CRUD functionality, ? which looks like this:
export default (state, action) => {
switch (action.type) {
case "REMOVE_EMPLOYEE":
return {
...state,
employees: state.employees.filter(
employee => employee.id !== action.payload
)
};
case "ADD_EMPLOYEES":
return {
...state,
employees: [...state.employees, action.payload]
};
case "EDIT_EMPLOYEE":
const updatedEmployee = action.payload;
const updatedEmployees = state.employees.map(employee => {
if (employee.id === updatedEmployee.id) {
return updatedEmployee;
}
return employee;
});
return {
...state,
employees: updatedEmployees
};
default:
return state;
}
};
In our AppReducer.js
file, we added our switch case and wrote some functionality for each case and returned employee state inside respective functions. We’ll move ahead with our AddEmployee
component and write an onSubmit
handler which will push the filled values of our form field into the state.
Below is how our code looks like:
import React, { Fragment, useState, useContext } from "react";
import { GlobalContext } from "../context/GlobalState";
import { useHistory } from "react-router-dom";
import { Link } from "react-router-dom";
export const AddEmployee = () => {
const [name, setName] = useState("");
const [location, setLocation] = useState("");
const [designation, setDesignation] = useState("");
const { addEmployee, employees } = useContext(GlobalContext);
let history = useHistory();
const onSubmit = e => {
e.preventDefault();
const newEmployee = {
id: employees.length + 1,
name,
location,
designation
};
addEmployee(newEmployee);
history.push("/");
};
return (
<Fragment>
<div className="w-full max-w-sm container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="name"
>
Name of employee
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600"
value={name}
onChange={e => setName(e.target.value)}
type="text"
placeholder="Enter name"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="location"
>
Location
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={location}
onChange={e => setLocation(e.target.value)}
type="text"
placeholder="Enter location"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="designation"
>
Designation
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:text-gray-600"
value={designation}
onChange={e => setDesignation(e.target.value)}
type="text"
placeholder="Enter designation"
/>
</div>
<div className="flex items-center justify-between">
<button className="mt-5 bg-green-400 w-full hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Add Employee
</button>
</div>
<div className="text-center mt-4 text-gray-500">
<Link to="/">Cancel</Link>
</div>
</form>
</div>
</Fragment>
);
};
Here setName
, setLocation
and setDesignation
will access the current value we typed inside our form fields and wrap it in a new constant, newEmployee
, with unique id adding one to the total length. We’ll add a parameter to our GlobalContext
we imported and newEmployees as out parameter as it accepts employees as a payload inside our GlobalState
. Finally we’ll change our route to our main screen where we’ll be able to see the newly added employees.
We’ll go into our EditEmployee
component and write some functionality for editing the existing objects from the state. If you have noticed we added:
path=”/edit/:id”
To our App.js
file where we shall route to the route parameter. Lets take a look at following code
import React, { Fragment, useState, useContext, useEffect } from "react";
import { GlobalContext } from "../context/GlobalState";
import { useHistory, Link } from "react-router-dom";
export const Editemployee = route => {
let history = useHistory();
const { employees, editEmployee } = useContext(GlobalContext);
const [selectedUser, setSeletedUser] = useState({
id: null,
name: "",
designation: "",
location: ""
});
const currentUserId = route.match.params.id;
useEffect(() => {
const employeeId = currentUserId;
const selectedUser = employees.find(emp => emp.id === parseInt(employeeId));
setSeletedUser(selectedUser);
}, []);
const onSubmit = e => {
e.preventDefault();
editEmployee(selectedUser);
history.push("/");
};
const handleOnChange = (userKey, value) =>
setSeletedUser({ ...selectedUser, [userKey]: value });
if (!selectedUser || !selectedUser.id) {
alert("Id dont match !");
}
return (
<Fragment>
<div className="w-full max-w-sm container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="name"
>
Name of employee
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={selectedUser.name}
onChange={e => handleOnChange("name", e.target.value)}
type="text"
placeholder="Enter name"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="location"
>
Location
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={selectedUser.location}
onChange={e => handleOnChange("location", e.target.value)}
type="text"
placeholder="Enter location"
/>
</div>
<div className="w-full mb-5">
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor="designation"
>
Designation
</label>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:text-gray-600 focus:shadow-outline"
value={selectedUser.designation}
onChange={e => handleOnChange("designation", e.target.value)}
type="text"
placeholder="Enter designation"
/>
</div>
<div className="flex items-center justify-between">
<button className="block mt-5 bg-green-400 w-full hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:text-gray-600 focus:shadow-outline">
Edit Employee
</button>
</div>
<div className="text-center mt-4 text-gray-500">
<Link to="/">Cancel</Link>
</div>
</form>
</div>
</Fragment>
);
};
Here we used the useEffect hook, which is invoked when the component is mounted. ? Inside this hook we’ll know the current route parameter and find the same parameter to our employees object from the state. We then created the setSelectedUser
function and passed selectedUser
as its parameter. We then observe for onChange
events on our form fields where we pass on userKey and value as two parameters. We spread selectedUser
and set userKey
as value from the input fields.
Finally invoking the onSubmit
event works just fine. ?? Here we have successfully created our CRUD application using the Context API and hooks.
You can also find the code in a GitHub repository here.
Happy Coding!
This content originally appeared on Alligator.io and was authored by Alligator.io

Alligator.io | Sciencx (2020-03-16T00:00:00+00:00) Building a CRUD App with React Hooks & the Context API. Retrieved from https://www.scien.cx/2020/03/16/building-a-crud-app-with-react-hooks-the-context-api/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.