Before we dive into the development process, let’s make sure we have everything we need. Ensure that you have the following installed:
- Node.js and npm (Node Package Manager)
Order of execution
In a React app that utilises TypeScript and functional components without relying on class components, the order of execution follows a specific sequence:
Initialisation: When the React app is loaded, the React library initialises by creating the virtual DOM (VDOM), which is a lightweight representation of the actual DOM.
Mounting: The mounting phase begins by creating an instance of the root component specified in the ReactDOM.render() method. This component, written using TypeScript or pure JS/TS functions, is mounted onto the actual DOM, replacing the target HTML element.
Component Rendering: React traverses the component tree, starting from the root component. Each functional component’s body is executed, returning a description of the component’s user interface as React elements. This applies to both TypeScript and pure JS/TS functions used to define components.
Diffing and Reconciliation: React performs a diffing algorithm to determine the differences between the previous and new versions of the virtual DOM. This process, applicable to both TypeScript and pure JS/TS functions-based components, is called reconciliation. It identifies the minimal set of changes needed to efficiently update the actual DOM.
Updating: Once the differences are identified, React applies the necessary updates to the actual DOM, ensuring it reflects the new state of the application. The update process, which includes inserting, updating, or removing elements, is performed for both TypeScript and pure JS/TS functions-based components.
Unmounting: If a component is removed from the component tree, React cleans up any resources associated with that component. This is automatically handled by React when using functional components.
Event Handling: React attaches event handlers, whether defined in TypeScript or pure JS/TS functions, to the appropriate elements in the actual DOM. When an event occurs, React triggers the corresponding event handler defined in the component, allowing us to respond to user interactions.
State and Props Updates: When a component’s state or props change, React re-invokes the body of the functional component, resulting in a re-rendering of that component and its child components. This applies to both TypeScript and pure JS/TS functions-based components.
It’s important to note that the execution order in React may vary depending on the specific circumstances of the application, the usage of TypeScript or pure JS/TS functions, and the hooks employed. However, the outlined sequence provides a general overview of the typical execution flow in a React app utilising TypeScript and functional components without relying on class components.
Creating a new React project
To create a new React app with TypeScript and SCSS (Sass), we can use the create-react-app tool and specify the necessary configurations.
npx create-react-app todo-react-ui --template typescript
Lets start by creating a new .nvmrc file in the root of the project. This defines what version of node we are using. We are going to use the ‘lts/Gallium’ version. Add the following line to the file.
Run the following command and might prompt us to install the version if it is missing.
Clean up and initial set-up
npm install node-sass
npm install @mui/icons-material @mui/material
Let us create a new App.scss file and import that in the App.tsx file. Now this acts as a global stylesheet and all the generic SCSS classes can go in here.
Let’s add FontAwesome and flexboxgrid in the public/index.html. Add the following in the <head> tag.
<link rel=”stylesheet” type=”text/css” href=”https://use.fontawesome.com/releases/v6.2.1/css/all.css” defer/>
<link rel=”stylesheet” type=”text/css” href=”https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css” defer/>
FontAwesome is a powerful icon library that provides a wide range of visually appealing icons that can enhance the visual appearance of our application. By utilising FontAwesome, we can easily incorporate these icons into various parts of our application, adding a touch of style and clarity.
FlexBoxGrid, on the other hand, is a flexible grid system that leverages the power of the CSS flex display property. With FlexBoxGrid, we can easily create responsive and dynamic layouts for our application. It simplifies the process of arranging elements on the page, allowing us to achieve desired layouts with ease. By utilising the capabilities of FlexBoxGrid, we can ensure that our application’s layout remains consistent across different screen sizes and devices.
In our project, we have introduced a configuration file named “config.json” located in the “public” folder. This file contains environment variables that are utilised throughout the application, excluding any sensitive information. To handle the retrieval of these variables, we will implement a ConfigContext that will read the “config.json” file and provide access to these variables within our components. However, in the production environment, we need to override this configuration by introducing a separate production config file. This allows us to customise the variables specifically for the production environment, ensuring proper configuration and security measures are in place. By adopting this approach, we can effectively manage and utilise environment variables across different environments in our application.
To achieve a build-once, deploy-many scenario and ensure appropriate configuration for different environments such as staging, testing, and production, we can utilise multiple config.env.json files. Each environment can have its dedicated configuration file, allowing us to define specific settings and variables tailored to that particular environment.
By maintaining separate configuration files, we can easily switch between environments during the build or deployment process. This ensures that the correct configuration is used for each environment, avoiding any potential conflicts or inconsistencies.
Having separate config files for different environments simplifies the management of environment-specific settings, making it easier to maintain and update configurations without affecting other environments. This approach enhances the flexibility and scalability of our application, enabling smooth deployments and ensuring that the appropriate configurations are utilised based on the target environment.
Connecting to backend API
Our React application has been configured to send API requests to a specific target URL, which in this case is the todo-api server running on port 3000. This setup enables smooth communication between the frontend and backend, facilitating the retrieval and manipulation of data from the todo-api server within our React application. To handle the API requests, we will utilise React hooks and the axios library. React hooks provide a convenient way to manage state and side effects, while the axios library simplifies the process of sending XMLHttpRequest (XHR) requests to the API.
Component and its lifecycle
Function Body: The function body contains the logic and JSX code that defines the component’s UI and behaviour.
Initial Render: When the component is first rendered, the function body is executed, and the JSX elements are returned. This initial render creates the initial representation of the component’s UI.
Props Update: If the component receives new props from its parent component, it triggers a re-render. The function body is executed again, and the JSX elements are updated based on the new props.
State Updates: If the component’s state is updated using hooks like useState, the function body is executed again, and the JSX elements are re-rendered based on the new state values.
It’s important to note that in functional components, there is no concept of lifecycle methods like componentDidMount or componentDidUpdate. Instead, React provides hooks like useEffect, useState, useContext, etc., to handle side effects, manage state, and access context.
The useEffect hook allows us to perform side effects, such as fetching data from an API or subscribing to events, after the component has rendered or when specific dependencies change.
Let us create a set of shared components that promote component reusability and maintain consistency throughout the application. These shared components reside in the ‘src/components/’ directory and are imported wherever required.
Header serves as the navigation bar of the application, providing users with easy access to different sections or pages of the app. It typically contains menus, links, or buttons for navigation purposes.
Footer is responsible for displaying a footer at the bottom of the application, providing additional information, copyright notices, or links to relevant pages.
Heading is a reusable component used to display a heading or title for each page. It helps to maintain consistent styling and structure across different pages of the application.
Empty is designed to be displayed when a list or data set is empty. It provides a message or placeholder content to inform users that there is no data available.
TextModal is a dialog or modal component used to add or input data in the application. It typically includes form fields, buttons, and validation to gather user input and save it to the system.
TodoCard is responsible for displaying individual ToDos as aesthetically pleasing card designs.
The full code of these components is available in github.
Now let’s create the other pages that we are after.
We should create a directory with the name of the page, along with its SCSS styles, and TypeScript code.
The HomePage serves as the welcome or homepage of our application, acting as the landing page that users first encounter when accessing our site. It provides an overview of our app’s features, highlights, or any relevant information we want to showcase to users right from the start. This component is designed to provide a visually appealing and engaging user experience, making a positive first impression.
On the other hand, the NotFoundPage plays a crucial role as a fallback page. Whenever users navigate to a route that doesn’t exist or provide an incorrect route, they will be redirected to the NotFoundComponent. This component serves as an error page, informing users that the requested page or resource cannot be found. It helps to improve the user experience by gracefully handling such scenarios and providing a clear message to users about the issue encountered.
Elephant in the room, ToDo
Let us create a new modal, Todo.ts.
Let us create a React hook, src/hooks.todo.ts where we can define various functions to interact with the ToDo data. By utilising the power of React hooks, we can create a custom hook that encapsulates the logic for managing ToDo items. Within this hook, we can define functions such as getAllTodo, createTodo, updateTodo, and deleteTodo. The getAllTodo function retrieves all existing ToDo items, createTodo adds a new ToDo item to the list, updateTodo updates the details of a specific ToDo item, and deleteTodo removes a ToDo item from the list. By returning these functions as an object from the hook, we provide a convenient interface for other components to interact with the ToDo data. This approach promotes reusability and separation of concerns, allowing components to focus on rendering and user interactions while the hook handles the underlying ToDo data operations.
Let’s create a new ToDo Page with TodoPage.tsx inside the src/pages/TodoPage directory.
Within the TodoPage, we define several state variables using the useState hook, including activeTodos, inactiveTodos, activeTab, and modalOpen. The useEffect hook is used to fetch Todo data using the getAllTodo function from the useTodo hook when the component mounts. The retrieved data is then filtered into active and inactive Todo items, which are stored in the activeTodos and inactiveTodos states, respectively.
The component also includes various event handler functions that are responsible for handling tab changes, opening and closing the modal, submitting new Todo items, marking Todo items as active or inactive, deleting Todo items, and updating the Todo list.
The return statement consists of JSX elements that render the TodoPage’s UI. It includes a heading, a button to open the modal, tabs to switch between active and inactive Todo lists, and conditional rendering of TodoCard components or an Empty component based on the selected tab and the existence of Todo items. Finally, it renders the TextModal component if modalOpen is true.
To make our ToDo app visually appealing, we can apply custom styles using SCSS. Modify the SCSS file (TodoPage.scss) in the same directory to match our desired design and import this to TodoPage.tsx.
Our application incorporates the powerful features of FontAwesome and FlexBoxGrid libraries, enhancing the visual aesthetics and layout capabilities. By integrating FontAwesome, we gain access to an extensive collection of icons that can be easily incorporated into our UI, allowing for visually appealing and intuitive designs. Additionally, the integration of FlexBoxGrid provides us with a responsive grid system, enabling us to create flexible and adaptive layouts that adapt to different screen sizes. Furthermore, we have seamlessly integrated Material UI and its associated material icons, enriching our application with pre-designed components and a vast library of visually pleasing icons. Together, these integrations enhance the user experience and provide us with a rich set of tools to create visually appealing and responsive interfaces.
To enhance the navigation of our project, let’s incorporate routes with the following configuration:
- Assign the empty routes to the Home page, providing users with a welcoming starting point.
- Direct the todo routes to the Todo page.
- Map the 404 routes to an Error Page, ensuring a user-friendly experience when encountering unexpected routes.
- For any other routes that don’t match the defined paths, gracefully redirect users to the Error Page, maintaining a consistent flow throughout the app.
Let us create a AppRoutes.tsx component and import this in the App.tsx
Running the app
Let’s us run the app and have a look at it spinning up at http://localhost:3000
Testing and Debugging
During the development phase of our React app, it is vital to thoroughly test and debug it to ensure proper functionality. React offers robust testing tools such as React Testing Library, Enzyme and Cypress, which empowers us to conduct comprehensive testing. Create unit tests for our components and services to validate their behaviour and identify any potential issues that may arise. By running these tests, we can ensure that our React app functions as intended and maintains the desired level of quality and reliability. The combination of Jest, React Testing Library, and Enzyme is often a popular choice for comprehensive testing in React applications.
Building and Deploying the App
Once we are ready with our ToDo app, it’s time to proceed with the build and deployment process. React provides a seamless way to achieve this. Execute the following command in our terminal:
npm run build
This command initiates the build process and generates a production-ready build of our app. The resulting build files can then be deployed to a web server or hosting platform of our preference.
Great job! We have accomplished the task of building and configuring our very own React ToDo app. Throughout the process, we gained valuable insights into creating a new React project, establishing components, integrating functionality, styling the app, and deploying it. This marks the start of our exciting journey with React, and there’s a multitude of possibilities and knowledge awaiting us.
Continue to practise and delve into React’s extensive ecosystem, as well as leverage the official React documentation for further assistance. Embrace the joy of coding and relish in the convenience of using our newly created ToDo app.
API Code: https://github.com/rohithart/nestjs-todo
React Documentation: https://react.dev/learn