Forms are used almost everywhere, whether in an online booking portal, sign-up page, feedback review system, or any other form to collect data. Every form is built using a combination of operations that create, read, update, and delete, also known as CRUD. These operations are the most essential part of building web applications, and it’s important to understand how to handle them in a form.
In this guide, to keep things brief, we'll look at how to implement create and delete operations in a form component by building a simple and robust backend-less application. We'll learn to convert a basic form component to a controlled component that will perform create and delete operations, and we'll use a Redux store to retrieve data to the DOM through a reducer by dispatching actions for both the operations.
Make sure you have Nodejs and npm installed in your machine (at least version 8 or higher) along with a code editor and a web browser (preferably Chrome or Firefox).
In this guide, we'll build a simple todo application where users can add items to a list and delete them individually.
Create a new project using create-react-app
:
1npx create-react-app redux-todo
Install the dependencies Redux and React-Redux:
1npm install redux react-redux
To avoid writing down loads of CSS, use MaterializeCSS to quickly style your content. Include the MaterializeCSS CDN inside the index.html
file inside the public folder.
1 <!-- Compiled and minified CSS -->
2 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
3<!-- Compiled and minified JavaScript -->
4<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
Add some additional styles inside index.css
.
1.todo{
2 padding: 5px;
3 font-size: 20px;
4}
5
6.todo span{
7 float: right;
8 cursor: pointer;
9 height: 30px;
10 width: 25px;
11 padding: 2px;
12 background: tomato;
13 text-align: center;
14}
15
16input[type=text] {
17 height: 50px;
18 width: 40%;
19 padding: 10px 12px;
20 margin: 5px 15px;
21 margin-left: -60px;
22 box-sizing: border-box;
23 font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
24 border: 1px solid #404040;
25 font-size: 24px;
26}
27
28form{
29 padding: 10px;
30 margin-left: 320px;
31 max-width: 400px;
32}
The app will consist of two components: one to handle the form and another to render the list of todos. You'll create a store using Redux and have a rootReducer
to take care of generating new todos, deleting old todos, and adding todos to the list by dispatching their respective actions.
1redux-todo
2├── src -- src folder
3└── actions -- actions folder
4 ├── actionCreator.js -- the declaration of actions
5 ├── todoAction.js -- the definition of actions
6└── components -- components folder
7 ├── TodoForm.js -- form component
8 ├── Todos.js -- component for rendering todos
9└── reducers -- reducers folder
10 ├── rootReducer.js -- the definition of reducer
11
12
The todo schema will contain some text indicating the todo item along with an ID that will be used to delete the todos and uniquely distinguish them from each other.
Start by creating two action creators, one for adding an item to the list and another one for deleting an item from the list.
1export const ADD_TODO= 'ADD_TODO';
2export const DELETE_TODO='DELETE_TODO';
Import these action creators inside todoAction.js
and define two methods for each of the two actions.
1import {ADD_TODO} from './actionCreator';
2import {DELETE_TODO} from './actionCreator';
3
4export const addTodo=(todo)=>{
5 const action={
6 type:ADD_TODO,
7 todo
8 }
9 return action;
10}
11
12export const deleteTodo=(id)=>{
13 const action={
14 type:DELETE_TODO,
15 id
16 }
17 return action;
18}
The action for adding an item to the list will take the whole todo inside it, whereas deleting a todo will only need an ID.
Inside rootReducer.js
, import the action creators and define an initial state, which will be an empty array to begin with. Pass the actions inside the todos
method along with the state
using the initial state as a default parameter. For adding todos, invoke a function to return an object consisting of the todo item and a newly generated ID for that item using Math.random()
. For deleting todos, take id
along with state
and use the array filter
method to return a new array consisting of all the todos except the one to be deleted.
1import { ADD_TODO, DELETE_TODO } from '../actions/actionCreator';
2
3const initState=[];
4const todofn=(action)=>{
5 return{
6 todo:action.todo,
7 id:Math.random()
8 }
9}
10const deleteByID=(state=initState,id)=>{
11 const todos=state.filter(todo=>{
12 return todo.id!==id;
13 })
14 return todos;
15}
16const todos=(state=initState,action)=>{
17 let todos=null;
18 switch(action.type){
19 case ADD_TODO:
20 todos=[...state,todofn(action)]
21 return todos;
22 case DELETE_TODO:
23 todos=deleteByID(state,action.id)
24 return todos;
25 default:
26 return state;
27 }
28}
29
30export default todos;
Create a store
and provider
component to get a centralized data store inside index.js
.
1import { createStore } from 'redux';
2import {Provider} from 'react-redux';
3import rootReducer from './reducers/rootReducer';
4
5const store=createStore(rootReducer);
6ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
TodoForm.js
will be a class component to keep track of every new todo that is added using its local state.
1import React, {Component} from 'react';
2import { connect } from 'react-redux';
3import { addTodo} from '../actions/todoAction';
4import {deleteTodo} from '../actions/todoAction';
5import Todos from './Todos';
6
7 class TodoForm extends Component{
8 state={
9 todo:''
10 }
11 handleChange=(e)=>{
12 this.setState({
13 [e.target.id]:e.target.value
14 })
15 }
16 handleSubmit=(e)=>{
17 e.preventDefault();
18 this.props.addTodo(this.state.todo)
19 }
20 deleteTodo=(id)=>{
21 this.props.deleteTodo(id);
22 }
23 render(){
24 return(
25 <div className="container">
26 <form onSubmit={this.handleSubmit}>
27 <input type="text" id="todo" placeholder="what to do today?..." onChange={this.handleChange}/>
28 <button className="btn green" onClick={this.handleSubmit}>Add todo!</button>
29 </form>
30 <Todos todos={this.props.todos} deleteTodo={this.deleteTodo}/>
31 </div>
32 )
33
34 }
35
36}
37
38const mapStateToProps=(state)=>{
39 console.log('state',state);
40 return{
41 todos:state
42 }
43}
44export default connect(mapStateToProps,{addTodo,deleteTodo})(TodoForm);
The render
method returns some JSX for a form with an input field and an add button. Attach an onChange
event listener to the input field to set the state of the component every time it's triggered. Inside the form, use an onSubmit
event to invoke the handleSubmit
function. This function takes in the event
object to prevent the default reloading of the page and adds the state to the props by mapping the state of this component to the props using the connect
higher-order component. We also need to pass dispatch
as a second parameter, which takes the actions as key-value pairs (using ES6 shorthand notation) and passes the form component to the connect
method.
Pass the newly created todo from the form component as props to the todo component along with the deleteTodo
function.
1import React from 'react';
2
3const Todos=({todos,deleteTodo})=>{
4 if(todos.length==0)
5 return(
6 <p>No todos for today!</p>
7 )
8 else{
9 return(
10 <div>
11 <ul className="collection">
12 {
13 todos.map(todo=>{
14 return(
15
16 <li key={todo.id} className="collection-items">
17 <div className="todo">
18 {todo.todo}
19 <span onClick={(id)=>{deleteTodo(todo.id)}}>X</span>
20 </div>
21 </li>
22 )
23 })
24 }
25 </ul>
26 </div>
27 )
28 }
29
30}
31
32export default Todos;
After you take the todos and the deleteTodo
method as props, cycle through the list of todos, rendering each item on the DOM with a small x styled to look like a little delete button. When the user clicks this button, pass in the ID of that particular todo to the deleteTodo
method and dispatch the deleteTodo
action to the reducer.
Run npm start
to spin a local development, and you can see the app up and running on localhost:3000
in your browser (create-react-app automatically does this for you). You can add items by typing in the input field and delete them by clicking on the x for every todo.
The input field should clear itself or reset after a user clicks add. This can be achieved using a simple DOM query. All the todo data is stored locally inside the Redux store; to make sure the data persists on the DOM, you can hook it up to session storage, local storage, or a database. The initial state of the todos can then read this data from that database and add the read operation to this application. If you want this application to be backend-less and still wish to implement a read operation, the easiest way to do this is to add some hard-coded data to the initial state of the todos.
The application can be extended to make the todos act as reminders by attaching a deadline field and making it a progressive web app that sends a notification when a deadline is met. You can also add an option to edit those deadlines, thereby performing an update operation.
This guide has explained the most logical and convenient way to handle create and delete operations inside a form component. It has explored a methodical approach to create items, render them on the DOM, and delete them by building an application whose features can be extended to make a real-life web application. All applications today use a central state management tool like Redux, and controlled components make it simple to store stateful data, map the state to props, and dispatch actions to the reducer. Another popular method is to use Redux Forms, a Redux library that helps you manage the state of your forms using their API.