In this post let us see how we can easily build a task management app (which is totally & completely different from a todo app) using ReactJS. We will be using React Hooks, Chota CSS for styling and a lot of ES6+. We will not look at any centralised state management, or deal with a backend to store the tasks in this post.
Get Started
Use create-react-app
to structure your project like any sane person would do -
|
|
Proceed to have a dozen cups of coffee while your app gets initialised. Open the project root folder in VSCode to see this beautiful structure.
Start your app..
|
|
Navigate to http://localhost:3000
in your browser to see the app.
While the create-react-app seems to do a lot of things (it does), you only need to care about a few things at this time -
- The app gets anchored with one HTML file
public/index.html
and in a single node<div id="root"></div>
(for the most part, bear with me). The file itself will not have direct reference to the javascript - that will be done through a build step src/index.js
refers to theroot
element1 2 3 4 5 6
ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
App
inindex.js
is where you will start coding React magic. You shall use JSX in the React components to do all that magic, or you shall perish -1 2 3 4 5 6 7
function App() { return ( <div className="App"> <header className="App-header"></header> </div> ); }
Read more about JSX here, or just follow along by keeping mind two things -
- You can code all the HTML you want within JSX
- Use
className
instead ofclass
to reference css classes - Use
{ }
notation within JSX for expressions, variables etc.
Let’s do a quick take on how the components work. Create a new file src/components/HelloWorld.js
-
|
|
JSX can have only one root - <></>
tags take care of that. Rest is just plain HTML.
Include HelloWorld
in App.js
-
|
|
You can see the bold words in your app almost immediately.
Before we proceed, install a few things to make your React development easier -
- Install React Developer Tools. This will add a couple of tabs in your Chrome Developer Tools to view components. By default you should see
Components
andProfiler
- Install VSCode extension ES7 React/Redux/GraphQL/React-Native snippets that will give you a bunch of useful snippets
You can use Emmet scripts to quickly create boilerplate and scripts - for e.g. create a new file and type in rfce
and tab to see it in action.
Let us also include some styles, because well, we’re not cave-men.
Include the below line in your index.html
to include a small, class-light library called chota.css
-
|
|
Create a new file src/assets/styles.css
and leave it blank for now. We will use it for custom styling. Include the file in App.js
-
|
|
I will not really touch upon this topic again - but you can see the CSS code in the Github repo.
Creating Structure for your App
Let us create the basic elements for our app. First, the header - create a new file called src/components/Header.js
.
|
|
We have seen this earlier - JSX and stuff. The only difference is the CSS classes. Let’s include this Header
in App.js
.
|
|
The above changes should give you a good idea about where we are going. We create a Header
component, and include it in the main App
so that it’s available everywhere in the app.
We can now include the main content just below Header
.
Create Tasks Component
The simplest way to create a task component is familiar to us by now. Create a new file src/components/Tasks.js
-
|
|
Include Tasks
in App
.
|
|
You should now be able to see the tasks and a button “New” that doesn’t do anything.
Let’s level up. We will create a state variable tasks
that will store tasks and can be accessible from different components. We could eventually use this variable to store tasks retrieved from a database or a file.
In the simplest design we could create tasks
in Tasks
component, but that would make it difficult to access in a different component. For example, we need tasks
in both Tasks
, which is a list of tasks, and TaskDetail
, which shows the detail task and allows user to edit tasks. So, let’s create tasks
in App.js
for now.
|
|
We have done a couple of cool things here -
- Import
useState
from React - Create
tasks
and initialize it.tasks
is the variable name andsetTasks
is used to set this variable with a value. We will not be usingsetTasks
right now, but directly provide the initial value as a param touseState
1 2 3 4
const [tasks, setTasks] = useState([ { desc: "Learn React", id: 1 }, { desc: "Profit", id: 2 }, ]);
- Pass
tasks
as props to theTasks
component
Let us receive tasks
prop in our Tasks
component.
|
|
As you can see -
- We receive a destructured
tasks
prop in the function. You could also define param as simplyprops
(no brackets, no destructuring), and refer to tasks asprops.tasks
- We cycle through
tasks
and display individual task usingh3
element. The brackets contain the overall expression in JSX1 2 3 4 5 6 7 8 9
{ tasks.map((task) => { return ( <div className="col-12 text-left" key={task.id}> <h4>{task.desc}</h4> </div> ); }); }
You should now see..
Create Task Component
Tasks
component is doing too many things -
- List tasks
- Show task
- er.. that’s about it, but it has the potential to much more
Let’s break the component so that display of a task becomes concern of a different component.
Create a new file src/components/Task.js
-
|
|
The code is a direct copy of the fragment responsible to display a specific task in Tasks
component.
Include Task
in Tasks
-
|
|
This is very similar to last time when we included one component in another. We are just using an additional attribute called key
, which denotes the unique identifier of each element in the array (and that will be task.id
in our case).
The added advantage of segregating task display is that we can use this Task
component to display individual task anywhere in the application.
Fantastic.
Let us add more attributes to the task and beautify this a bit. Afterall, a task is not simply about the description.
Add more attributes to the task in App
.
|
|
Change Task.js
to make tasks come alive -
|
|
And, voila..
Add Functionality: Complete Task
We have successfully displayed a task so far, but we don’t have any functionality enabled on the task. We will change that by inserting a button to toggle the task status.
Add a function that can get called to change status. If we had a global state (e.g. Redux), we would have more independence to decide where this function has to reside. Since we are using a simple state at App
level, we will introduce the function to change that state also within App
. Create a new function -
|
|
The child component (Tasks
in our case) does not know about this method. We have to pass the function to the inner-most component that will call it for some greater good.
Change App.js
to pass onTglStatus
as a prop -
|
|
Repeat the above code in Tasks.js
-
|
|
Call the function in Task.js
-
|
|
Click the button to see the debug statement in developer console.
We have moved props including variables(state) and functions that act on the state down the chain, and events get surfaced up the component chain!
Add the function to do something beyond just a debug statement in App.js
-
|
|
In the above code we use Array.map
function to change status for the matching record. The matching record (task
) is supplied by the function called by button click event.
Let us change the checkbox/button in indicate completed tasks in Task.js
.
|
|
We use one of the old Javascript tricks to display ✅
when task is complete.
{task.complete && "✅"}
evaluates to✅
whentask.complete
is true. Else this returns nothing- The next expression
{!task.complete && "⬜"}
returns⬜
whentask.complete
is false
Since our application is reactive to changes and the button label gets driven by data, we see the below behaviour.
Add Functionality: Add Task
We need to get task data from user and save new tasks. To that end, add the function to save tasks onSaveTask
in App.js
-
|
|
We are using setTasks
like before, but this time we are adding a new element to the array. Again, we don’t add the element directly to array since the state variable is immutable. We rather do this -
- create a new variable
- Add
{ desc: desc, date: date, id: Date.now(), complete: false },
as the first element.desc
anddate
are passed along by the form used to create new tasks.Date.now()
is a simple way to make the id unique - Add the existing array elements to the new array variable
...tasks
Next, add a new form to collect data. Let’s create a new component src/components/TaskEdit.js
. This component will receive the function onSaveTask
as props and call it on button click.
|
|
We have gone through some of the functions implemented here, while rest are mostly HTML -
const [desc, setDesc] = useState("");
creates a new state variale local toTaskEdit
and initiates it to""
- a standard HTML form displays a couple of text boxes to collect
desc
anddate
Save
button calls a local methodsaveTask
, which will call theonSaveTask
function inApp.js
and resets form
Add functionality to New
button so that it displays the TaskEdit
component and associated form (and can also toggle to hide the form).
In App.js
-
|
|
We conditionally show TaskEdit
only if showTaskEdit
is true
.
Time to see the complete application in action -
Next Steps
- Enable user to edit tasks to change description and dates
- Add a backend for your task manager
Code for this project is available in the Github repo.