Task Scheduling with React

Learn proper React architecture and practices, in only 15 minutes.

Emily Yu
Major League Hacking

--

My name is Emily Yu, and I’m a Coach at Major League Hacking. Like you, I’ve been attending Zoom University, and keeping track of all the deadlines and small tasks has left me in the dust. Through React, we’ll spin up a front-end interface to set a countdown to keep track of everything on a modifiable list of tasks.

Before we get started, feel free to reference the source code if you get stuck, which is located at https://github.com/emily-yu/mongo-scheduler.

Getting Started with React

create-react-app is an easy way to get started with React.js, as it sets up all the small nuances to create a clean React application for you, and quickly gets you started with some runnable boilerplate code. In this tutorial, we’ll start out with using create-react-app, then customize it to fit our needs.

Step 1. Ensure that all libraries are installed.

First off, check to see if npm, or the node package manager, is installed on your system — we will need this for both storing modules as well as creating the boilerplate React application. Run the following commands to ensure that npm is installed.

which node
node — version

If these commands do not yield a version, install the latest version using the following command.

npm install npm@latest -g

Step 2. Run create-react-app.

cd ~/desktop
npx create-react-app react-scheduler
cd react-scheduler
npm start

If you open https://localhost:3000, you should be able to see your React application with a spinning React logo, as seen below.

Step 3. Remove Boilerplate Code Elements.

Now that we have a runnable application up, we’ll clear out the base content (goodbye, React logo) and start with a fresh slate.

In App.js, the bulk of content you see on the screen is linked through it. The render() function, located at the bottom of the component is where we’ll be laying out the visual elements in our component. If you’re familiar with typical web development, in this section, you would typically write what you would put into index.html directly into the render function. However, while JSX mimics the syntax of HTML, which you would see in traditional web development, React instead performs operations using its own syntax, based on what is written inside the render function.

For example, when you write:

<p>Hello, World</p> 

…in React, it is converted to the React syntax:

React.createElement(‘p’, ‘Hello, World’)

That means for every HTML tag written, there is instead a createElement operation performed by React to generate the components.

The render() function, located at the bottom of the component, is where you’ll put the JSX, which syntactically operates like HTML, but and lay out the visual elements in our component. If you’re familiar with typical web development, in this section, you would typically write what you would put into index.html directly into the render function.

With that understanding, let’s move on to clearing out the boilerplate code. First, delete lines 7–20 to remove the entirety of what you currently see on the page, so that we can replace it with our new user interface.

<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer">
Learn React</a>
<Example6/>
</header>

After deleting, that block in your file should just have the following code inside of the return statement, which tells the application what to show from that file.

<div className="App">
</div>

At the moment, a lot of the other code you see inside that application pertains to the intro screen’s css/logic, but for the purposes of our tutorial, we do not need to modify those files.

React Architecture

Before we start creating our application, to better understand what we’re doing, it’s good to have a baseline understanding of how the files are structured.

React utilizes a component framework, which allows us to separate all the code for the different elements you see on the screen into different files. App.js is the base of the application, and we’ll be putting the instances of our components in there using the convention, <ComponentName/>.

You might be wondering, why is there a backslash in the tag, as opposed to the standard opening and closing tags of HTML? React tags can be self-closing, as opposed to a normal tag, which contains both a <ComponentName></ComponentName>. Self-closing tags receive data differently from normal tags, who access data through a property called children. By using self-closing tags, we simplify the component, ensuring that the tag does not have access to any properties.

To decide what items to make into components, we will consider two factors: 1) the reusability of each component, and 2) the functionality of any related items.

When looking at the reusability of each component, we consider the following questions.

  1. How often are we going to use this component?
  2. What elements of this component can be used in other components? Are the elements too different for us to have to write the component from scratch?

As opposed to the reusability of each component, we consider a different set of factors for the functionality of related items. The functionality refers to the type of data being passed through, and how the data interacts with the environment.

With the bulk of the architecture out of the way, let’s get started by making a folder under src/components, where we will store all of the different componentized elements of the page.

In this app, we will need three components on the user interface.

  1. A container to hold the countdown items.
  2. A countdown item.
  3. A place to add countdown items to the container.

Creating the Application Components

Let’s start off by making the interface for a singular countdown item. For each countdown, let’s call it a “Task.”

Part 1. Initializing Task.js.

First, make a file under the components folder, Task.js, which will contain all the logic for our countdown component.

Next, copy-paste the following code into your new Task.js file.

// Section 0import React, {Component} from ‘react’;
class Task extends Component {
// Section 1
constructor(props) {
super(props);
}
// Section 2
render() {
return (
<div>
<p>Task Name</p>
<p>Remaining Time: 999ms</p>
</div>
)
}
}
// Section 3
export default Task;

Let’s go through this block of code, section by section.

For Section 0, the barebones functionality is created. We import the React frameworks that we’ll be needing, which has features that allow us to construct our application with the correct React architecture. In this instance, we are destructuring our import and pulling the named ‘Component’ export from React. This import is used in the next line of code where we name our class. The new class extends the properties of React.Component, a set of functionality that allows us to declare a constructor, use props, and use state in our component (more on this later).

For instance, Section 1 is an immediate use of Section 0, as it implements the architecture for components. The function header constructor(props) brings props, or properties, from the component it was initialized in. That component then specifies information and passes it down to the child component.

In Section 2, we deal with the meat of the code. For now, we’ll leave the content as some filler stubs so that we are able to see our component on the page. The JSX inside the return() function will be sent back to the main page where we declared the Task class which will put the ‘HTML’ we write in render() into the main App. As for the content, we’ll be replacing the 999 with the actual time to countdown later.

The last line, or Section 3, exports the class that we have written, so that we can import it wherever we want to use it.

Part 2. Implementing Task.js into our Application.

Let’s import the class that we just made.

import Task from ‘./components/Task’;

Create an instance in App.js, by putting <Task/> into the divs so it can be rendered into the app. Your code should look like the following:

return (
<div className="App">
<Task/>
</div>
);

If you reload, voila — there’s a clean page with a task inside!

Part 3. Initializing TaskList.js.

But, we don’t want to have a ton of tasks just floating around the application. Let’s make the container component, which will contain the Tasks that we made in the previous section.

First of all, create the file, components/TaskList.js

Next, we’re going to need to import Task into TaskList, in addition to React.Component. Use the following to add the class to your new file.

import React, { Component } from ‘react’;
import Task from ‘./components/Task’;

Now that we have all the needed libraries, copy paste the following header code into your TaskList.js file to create a new class.

class TaskList extends Component {
// Section 0
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>tasklist</h2>
</div>
)
}
}
export default TaskList;

The structure of the code in TaskList.js should look similar to what we did in the previous section, for Task.js. Now that we have a class whose function is to take in data and turn it into Task, let’s add some dummy data into App.js, so that we can populate our TaskList with some dummy data and ensure that the functionality works.

Add the following lines to App.js, under the function in Section 0 and after the super(props) line.

this.state = {
tasks: [
{ taskName: "this is a task!", timeRemaining: 3 },
{ taskName: "this is another task!", timeRemaining: 7 }
]
};

You might be wondering, what is this new state property? Though props (properties) and state variables seem like they would be similar, properties are reserved for data being passed down from the parent component, whereas state is managed directly by the child component itself. When the state changes, the component will re-render.

Right now, there is no interaction within the components. The primary goal is to implement Task.js into TaskList.js, and we made some dummy data that we want to turn into instances of Task.js from within TaskList.js. But how will we add in the Task data from the main App.js?

The answer is: mapping!

Part 4. Mapping Data to Components.

Mapping iterates through each of the elements in an array and sends them to a function.

Taking a look at the code that we’ll be adding to TaskList.js, each task is defined as an “item” and is passed to the anonymous function that returns a <Task> object. The property values of items are extracted using brackets { }, and each Task object sets a property of each Task to the appropriate values.

{
// for each of the tasks received from main App
this.props.tasks.map(item => {
// create a <Task/> countdown element using properties
return <Task name={item.taskName}
timeRemaining={item.timeRemaining}/>
})
}

Each of the properties listed inside the Task tag are the item’s values (name, timeRemaining), and are what make up the child Task component’s “props.” That means, in the Task.js that is created from our TaskList.js mapping, item.taskName and item.timeRemaining can be referenced inside the constructor by accessing the name and timeRemaining property of the props variable.

The props object ultimately being passed down to Task.js instance looks like the following.

this.props = {
name: item.taskName,
timeRemaining: item.timeRemaining
}

When you’ve gone through all these steps, your TaskList.js implementation should include the following code.

import Task from './components/Task';
import React, { Component } from 'react';
class TaskList extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>tasklist</h2>
{
this.props.tasks.map(item => {
return <Task name={item.taskName} timeRemaining={item.timeRemaining}/>
})
}
</div>
)
}
}
export default TaskList;

Part 5. Implementing TaskList.js into our Application.

Now that we have a container for many tasks, let’s replace that individual <Task/> from before with a new <TaskList/>. Can you guess how we would set this.props of TaskList to take in the data from the parent, the this.state.tasks variable of App.js?

import TaskList from ‘./components/TaskList’
<TaskList tasks={this.state.tasks}/>

That’s right, and we would pass it in through the “properties” of TaskList, which makes it accessible by this.props later on. With the new tasks property, we’ll need to implement it so that the values in Task.js are no longer hardcoded, and instead use the values in this.state.tasks. To do so, all that’s needed is to reference props variables on top of the original placeholder values that we originally had, as React interprets items inside brackets to be variables.

return (
<div>
<p>Task Name: {this.props.name}</p>
<p>Remaining Time: {this.props.timeRemaining}ms</p>
</div>
)

Your Task.js looks like the above. With all the above changes made, once you reload the screen, you should see two tasks initialized with the data in this.state.tasks.

Part 5. Creating an Interface for Inputting Tasks.

Right now, our data is hardcoded. That means that the TaskList can only be changed by modifying the source code, and we can’t add any new tasks. The goal of this component is to be able to input data through some component, and update the data in the parent App.

First of all, create the component, under components/TaskCreator.js

Now that we have a file ready for implementation, add the boilerplate imports, as exhibited from our previous components.

import React, { Component } from 'react';class TaskCreator extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h2>new task</h2>
<input type="text" placeholder="title"></input>
<input type="text" placeholder="duration (in seconds)"></input>
<input type="button" value="done"></input>
</div>
)
}
}
export default TaskCreator;

There’s not much that is shockingly different here in comparison to the other components that we’ve made, as the syntax for class declarations and imports are similar to those of the previous components. You will, however, notice that we have 2 input fields and a button to submit to create a new task to input, which will be addressed in the following section.

Part 6. Implementing State Tracking.

In React, we don’t query the DOM by property values, like we would in vanilla HTML/CSS/JS. In this case, we should keep track of the state of the input boxes by constantly updating the state of the component when the user inputs a new character, until time of submission.

To track when an input box changes, add the onChange property to the input boxes that we created in the previous step.

<input type=”text” placeholder=”title” onChange={this.updateParams}></input><input type=”text” placeholder=”duration (in seconds)” onChange={this.updateParams}></input>

When we submit the data to create a new Task, we are going to track a different user interaction, and will track when the user clicks the button.

<input type=”button” onClick={this.createTask}></input>

For the added functions, let’s take a look at a quick rundown of what they do.

  1. The placeholder property is the background text that is removed as the user starts typing
  2. The onChange property triggers the function that it equals when the value of the input is changed. In this case, it would be when the user types anything into the box.
  3. The onClick property triggers the function when the input button is clicked.

For these inputs to work, we’ll need to define the 2 functions that the onChange and onClick properties map to: updateParams() and createTask().

Part 7: Linking Parent and Child Functions

To send data to the parent to create a new task (createTask) from TaskCreator, we will need to modify the state of the main App. However, the child component doesn’t have access to the parent’s state, and the child component can’t pass props back upwards to the parent component.

To solve this, we link the functions from the parent function and the child function. Before we do anything, let’s set up the connections between function and component.

Start off by creating the function headers as their own stubs, outside of the constructor but above the render, in the class body.

createTask = () => {...}
updateParams = (event) => {...}

Now, let’s take a look at the bodies of the functions, which will track changes in the input fields, which will eventually be tied together with the createTask and updateParams functions we just made.

Part 7a. Adding addTask() in App.js.

For a task to be added, we must track two pieces of information: task name and the time remaining, which will be the inputs to the addTask function.

addTask = (taskName, timeRemaining) => {   
// Section 0: update TaskList.js data structure
this.state.tasks.push({taskName, timeRemaining})
this.setState({tasks:this.state.tasks})
// update tasklist item (happens thorough setState)
}

In the annotated Section 0, the first line updates the properties, and the second line sends the update to the state property itself. The functionality of these two lines is to set the state, which when changed, will automatically update the interface of the TaskList, which is linked to the state.

Part 7b. Adding updateParams() in TaskCreator.js.

Adding a new task only requires us to submit a set of static information, but when we are constantly updating the field, the state information is constantly changing. When the onChange function is triggered by a user inputting a change, the updateParams function is triggered to update the state accordingly.

The following code first checks which input field was updated, and then updates the state for the correct field. If the “title” is inputted, then the first branch is accessed. The only other branch accessed is the “duration (in seconds) branch,” so the else branch refers to that input.

// check which input field was updated, update state for that field
if (event.target.placeholder == "title") {
this.setState({
// event.target is the caller field
// event.target.value is the caller field's value; that is,
the user's input in the box
taskName: event.target.value
})
}
else { // is duration input field
this.setState({
timeRemaining: event.target.value
})
}

There is a new variable in this function: event.target. The onChange function has details on the event by default, including the component input that triggered the event. That means that we can extract the input information by referencing the event’s sender, event.target, and taking its value.

For simplicity, let’s break it down.

  1. The event field is the event that was triggered, so in this case it would be the triggering of the onChange function.
  2. The event.target field is the component in which the event was triggered, so the input field that triggered the onChange function.
  3. Thus, event.target.value is the value of the component that triggered the event, or the value of the input field itself.

To keep things simple, we create only one function for reading parameters, since both conditions check for state values. We use conditionals to differentiate between parameters by checking the placeholder property, the unique property of each input.

The state is then set in preparation to send to the parent.

Part 7c. Adding createTask() in TaskCreator.js.

To establish a connection between the parent and the state values set by updateParams(), we utilize the createTask(). This function calls the parent-specific function that was passed down to the child, createTask(taskName, timeRemaining). The function differs from the standard createTask function in its parameters, as it adds the TaskCreator state data to the App state data.

The function addTask is located in App.js, so we cannot directly access the functionality in the child component. Thus, we must instead access the function through the child component’s props, which are passed down from the parent components, granting the child access to its properties.

this.props.addTask(this.state.taskName, this.state.timeRemaining)

We’re going to want to ensure that the fields are both filled, so let’s add a null check to ensure that the data is valid before creating the Task.

// ensure that both fields are filled out
if (this.state.taskName == "" && this.state.timeRemaining == ""){
console.log("error")
}
else {
// reference the function from App to call function reference
this.props.addTask(this.state.taskName,
this.state.timeRemaining)
}

Part 8: Adding the Countdown Functionality

As any timer would, we will want to execute a function every second. To do this, we will utilize the javascript function, setInterval(function, timeInterval).

The function setInterval operates in milliseconds, and 1000 milliseconds is equivalent to 1 second. Since our input displays in seconds, we will set the timeInterval to be 1000 milliseconds as we’ll be updating the time left after every second.

setInterval(() {// <logic-to-adjust-time>}, 1000)

To appropriately update the values on the TaskList every second interval, we’ll need to update the state at these intervals. So, we’ll need to setState every second, so that the time left will update appropriately. To do this, add the following code in your constructor for App.js.

// timer countdown to decrement tasks each secondthis.interval = setInterval(() => {for (let [index, task] of this.state.tasks.entries()) {// perform the operationthis.state.tasks[index].timeRemaining -= 1// if time is completed…if (task.timeRemaining <= 0) {// remove the taskthis.state.tasks.pop(index)}this.setState(this.state.tasks) // update state = update tasklist}}, 1000)

Let’s go through this code line-by-line.

The for-loop iterates over both the index/key and value of that key in the dictionary. By using .entries(), we are able to split up the key and values of the dictionary into two variables, which automatically assign to index and task inside of the square brackets. The rest of the lines inside of the for loop operate on every task, because the for loop will make one iteration for every Task in its TaskList.

That being said, the next line subtracts 1 second from the current task’s remaining time.

The if statement pops the task out of the tasks list if it reaches 0 so that when re-rendering, the task will effectively be deleted.

Congrats — you should now have a fully functional countdown app built in React.js! If you thought this tutorial, please let me know with a 👏 .

Feel free to reference the full source code if you get stuck or leave a comment on this tutorial.

--

--