---
tags: ironhack, lecture
---
<style>
.alert-info.lecture,
.alert-success.lecture,
.alert-warning.lecture,
.alert-danger.lecture {
box-shadow:0 0 0 .5em rgba(64, 96, 85, 0.4);
position:relative;
ddisplay:none;
}
.alert-info.lecture:before,
.alert-success.lecture:before,
.alert-warning.lecture:before,
.alert-danger.lecture:before {
content:"👨🏫\A"; white-space:pre-line;
display:block;margin-bottom:.5em;
/*position:absolute; right:0; top:0;
margin:3px;margin-right:7px;*/
}
</style>

# React | Integrating the React App
## Learning Goals
After this lesson, you will be able to:
- Make a request to a REST API using `axios`
- Understand how to integrate backend and frontend parts of your application
## Introduction
In this lesson, we will build a simple `React` application to consume our REST API. We will have a page showing all the projects in the database. Selecting one of them will show all the tasks and its details and will allow you to edit and delete projects as well as the tasks inside the projects.
Let's start with creating the React app using CLI command:
:::info lecture
Notre serveur d'API en place, passons maintenant à la partie cliente `project-management-client`...
:::
```shell
$ cd ~/code
$ npx create-react-app project-management-client
$ cd project-management-client
$ code .
```
We already updated the port where our server side is running-we changed it from `3000` to `5000`, so we don't need to make any changes on our client side. To resume:
- `project-management-server` runs on `http://localhost:5000`
- `project-management-client` runs on `http://localhost:3000`
To run our server we use `npm run dev` and to kick off our client, we use `npm start`.
:::success lecture
On doit au final avoir 2 serveur qui tournent, l'un sur 5000 (serveur d'API) l'autre sur 3000 (React serveur)
:::
:::info lecture
Plan :

:::
### Projects
We won't dig into the routes just yet, but for now we will create two components, to create project and to display project, and we will take it from there.
We will be using `axios` to make the call to our server side and for that purpose, we need to install it (run the following line in client's terminal):
:::info lecture
On utilisera axios pour faire nos calls à notre API depuis React:
:::
```shell
$ npm install axios
```
:::info
Remember to import `axios` on the top of the each component that is communicating with backend.
```jsx
import axios from 'axios';
```
:::
Since we know that we will have some routes, let's install `react-router-dom` here as well:
:::info lecture
On utilisera également le router React :
:::
```shell
$ npm install react-router-dom
```
So let's start off: inside `src` folder create `components` folder and in it create `projects` folder. Inside `projects` folder, create `AddProject.js` and `ProjectList.js`. So far, we have this structure:
:::info lecture
Créons nos composants pour les projets rangés dans `src/components`:
```shell
$ mkdir -p src/components/projects
$ touch src/components/projects/{AddProject,ProjectList}.js
```
:::
```
src
├── components
└── projects
└── AddProject.js
└── ProjectList.js
```
We will start with building `<AddProject />` component. This component will display form and will handle its submission. By handling the form submission, we mean using axios to reach a backend route and deliver some data sent from the frontend (or we can just say sent by user after filling the form and submitting it). So let's do this:
:::info lecture
<span style="font-size:500%;">1️⃣</span>
Commencons par notre formulaire d'ajout d'un projet `<AddProject>` :
:::
```jsx=
// components/projects/AddProject.js
import React, { Component } from 'react';
import axios from 'axios';
class AddProject extends Component {
state = { title: "", description: "" }
handleFormSubmit = (event) => {
event.preventDefault();
//
// AJAX request below
//
const title = this.state.title;
const description = this.state.description;
axios.post("http://localhost:5000/api/projects", { title, description })
.then(() => {
// this.props.getData();
// Reset form
this.setState({title: "", description: ""});
})
.catch(error => console.log(error))
}
handleChange = (event) => {
const {name, value} = event.target;
this.setState({[name]: value});
}
render(){
return(
<div>
<form onSubmit={this.handleFormSubmit}>
<label>Title:</label>
<input type="text" name="title" value={this.state.title} onChange={ e => this.handleChange(e)}/>
<label>Description:</label>
<textarea name="description" value={this.state.description} onChange={ e => this.handleChange(e)} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
export default AddProject;
```
:::info
Side note: Leave `this.props.getData()` commented out for now, soon we will need it.
:::
Most of the code looks very familiar right? Because we already covered basics of using forms in React.
:::info lecture
Instancions notre composant `<AddProject />` dans `App.js` :
:::
Let's import `<AddProject />` component into `App.js` and render it there for now.
```jsx
// App.js
import React, { Component } from 'react';
import './App.css';
import AddProject from './components/projects/AddProject'; // 👈
class App extends Component {
render() {
return (
<div className="App">
<AddProject /> {/* 👈 */}
</div>
);
}
}
export default App;
```
Now you can go to `http://localhost:3000` and you will see a (very ugly) form there.
:::success lecture
Lançons notre `npm start` et vérifions que nous pouvons créer un projet :

:::
:::success lecture
Vérifions d'ailleurs que notre requête AJAX (axios) a bien eue lieu :

:::
Make sure your server is running and try to add a new project.
Check your database and you should see new project in `projects` collection.🎯
:::success lecture
Vérifions enfin que le projet est bien persisté en base :

:::
:::success lecture
<span style="font-size:300%;">🙌</span> Notre application React interagit avec notre API
:::
---
So when we have at least one project in our database, let's proceed to creating `<ProjectList />` component. Inside this component we will use axios to get all the projects from backend.
:::info lecture
<span style="font-size:500%;">2️⃣</span>
Maintenant, créons notre composant listant les projets `<ProjectList>` dans `src/components/projects/ProjectList.js` déjà créé :
:::
```jsx
// components/projects/ProjectList.js
import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import AddProject from './AddProject'; // <== !!!
class ProjectList extends Component {
state = { listOfProjects: [] }
getAllProjects = () =>{
axios.get(`http://localhost:5000/api/projects`)
.then(responseFromApi => {
this.setState({listOfProjects: responseFromApi.data})
})
.catch(err => console.log('Error', err))
}
componentDidMount() {
this.getAllProjects();
}
render(){
return(
<div>
<div style={{width: '60%', float:"left"}}>
{ this.state.listOfProjects.map( project => {
return (
<div key={project._id}>
<Link to={`/projects/${project._id}`}>
<h3>{project.title}</h3>
</Link>
{/* <p style={{maxWidth: '400px'}} >{project.description} </p> */}
</div>
)})
}
</div>
<div style={{width: '40%', float:"right"}}>
<AddProject getData={() => this.getAllProjects()}/> {/* <== !!! */}
</div>
</div>
)
}
}
export default ProjectList;
```
:::info lecture
review du code :
:::
:::info
Let's see what is interesting here:
- we use `componentDidMount()` lifecycle method to fetch the data from API (this is very much standard practice);
- we use `map()` to list the projects (don't forget to give each element the database ID as the key with **key={project._id}**);
- we use `<Link />` component from `react-router-dom` library to be able to dynamically change URL and in our case, to go to project's detail page (that's our next thing to work on);
- we see that `<AddProject />` component will be actually nested inside `<ProjectList />` so let's go and remove it from `App.js` and also let's un-comment out `this.props.getData()` inside `<AddProject />`;
- we commented out project description line since we will be building the details page, description will be visible there.
:::
:::warning lecture
n'oublions pas de décommenter `this.props.getData()` L18 de `AddProject.js` afin de rafraichir la liste une fois le projet ajouté
:::
---
:::info lecture
<span style="font-size:500%;">3️⃣</span>
Mettons en place un router (afin de pouvoir gérer plusieurs "pages") :
:::
Before we go to `http://localhost:3000` and see the list of projects, first we have to set up `Router`. If you remember, we start of the process by setting up the core of our application, `index.js`, where we render mother `<App />`component.
```jsx
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {register as registerServiceWorker} from './serviceWorker';
import { BrowserRouter as Router } from 'react-router-dom'; // 👈
ReactDOM.render((
<Router>
<App />
</Router>
),
document.getElementById('root')
);
registerServiceWorker();
```
After importing `BrowserRouter`, we use it by wrapping up the *<App />* tag.
At this moment, our *App.js* looks like this:
:::info lecture
A la place de `<AddProject>`, instancions maintenant `<ProjectList>` dans `App` :
:::
```jsx
// App.js
import React, { Component } from 'react';
import './App.css';
import ProjectList from './components/projects/ProjectList'; // 👈
class App extends Component {
render() {
return (
<div className="App">
<ProjectList /> {/* 👈 */}
</div>
);
}
}
export default App;
```
:::info lecture

:::
---
Let's add another file in `components/projects` folder, a `ProjectDetails.js` and add just these couple of lines of code for now:
:::info lecture
Créons un nouveau composant pour le détail d'un projet :
```shell
$ touch src/components/projects/ProjectDetails.js
```
:::
```jsx
// components/projects/ProjectDetails.js
import React, { Component } from 'react';
import axios from 'axios';
import { Link, Redirect } from 'react-router-dom';
class ProjectDetails extends Component {
state = {}
render(){
return <div>Welcome to project details page!</div>
}
}
export default ProjectDetails;
```
---
Moving forward, inside `components` folder let's create another folder `navbar` and inside this folder let's create `Navbar.js`.
So far, we have this structure:
```
src
├── components
└── projects
├ └── AddProject.js
├ └── ProjectList.js
├ └── ProjectDetails.js
├
└── navbar
└── Navbar.js
```
Our `Navbar.js` will have some links down the road, but at this point only one actually:
:::info lecture
<span style="font-size:500%;">4️⃣</span>
Et maintenant une `<Navbar>` :
```shell
$ mkdir src/components/navbar
$ touch src/components/navbar/Navbar.js
```
:::
```jsx
// components/navbar/Navbar.js
import React from 'react';
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<nav className="nav-style">
<ul>
<li><Link to="/projects" style={{ textDecoration: 'none' }}>Projects</Link></li>
</ul>
</nav>
)
}
export default Navbar;
```
:::info lecture
Mettons à jour notre `App.js` avec tout cela : une `<Navbar>`, un routing entre la liste `<ProjectList>` et le détail d'un projet `<ProjectDetails>` :
:::
Now we have set everything so we can go to `App.js` to set up some routes there:
```jsx
// App.js
import React, { Component } from 'react';
import './App.css';
import { Switch, Route } from 'react-router-dom';
import ProjectList from './components/projects/ProjectList';
// 👇
import Navbar from './components/navbar/Navbar';
import ProjectDetails from './components/projects/ProjectDetails';
class App extends Component {
render() {
return (
<div className="App">
{/* 👇 */}
<Navbar />
<Switch>
<Route exact path="/projects" component={ProjectList}/>
<Route exact path="/projects/:id" component={ProjectDetails} />
</Switch>
{/* 👆 */}
</div>
);
}
}
export default App;
```
Okay, great! Also, don't worry that our `/` route is still unused, we are saving it for `login/signup` which will follow in the next lesson.
:::info lecture
Pas de route pour l'instant sur `/` mais on se la réserve pour le login/signup plus tard...
:::
For now we can: create new project, display all the projects and by clicking on project's title, we are redirected to *project details* page which for now doesn't have much code. Let's add some more!
:::info lecture
Finissons notre composant `<ProjectDetails>` en affichant les infos de notre projet :
:::
```jsx
// components/projects/ProjectDetails.js
import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
class ProjectDetails extends Component {
state = {}
// 👨🏫
componentDidMount(){
this.getSingleProject();
}
// 👨🏫
getSingleProject = () => {
const { params } = this.props.match;
axios.get(`http://localhost:5000/api/projects/${params.id}`)
.then( responseFromApi => {
const theProject = responseFromApi.data;
this.setState(theProject);
})
.catch(err =>console.log(err))
}
render(){
return(
<div>
{/*👨🏫*/}
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
<Link to={'/projects'}>Back to projects</Link>
</div>
)
}
}
export default ProjectDetails;
```
Pretty much straight forward: `componentDidMount()` is executing `getSingleProject()` method which initially communicates with our backend route through axios call. If everything is successful, we are updating the state (using nothing but *setState()*) and equaling it to the project object we got from our API. In the *render()* part, we are accessing to project's properties through `this.state.title` and `this.state.description`.
:::info lecture
Explication du code : qd le composant est "monté" dans la page (grâce à `componentDidMount()`), on effectue une requête `GET /projects/:projectid` (SHOW) afin de récupérer notre projet en base. On `setState` ensuite les datas récupérés que l'on utilise après dans le `render()`.
NB : `:projectid` est issu du routing via `props.match.params`
:::
---
Next challenge is displaying `edit form` and `updating` the project. The first of all, we will create new file inside `components/projects` folder - `EditProject.js`. This component will have the form that will *onSubmit* make changes to a specific project.
:::info lecture
Il nous manque maintenant un composant d'édition d'un projet, ajoutons le :
```shell
$ touch src/components/projects/EditProject.js
```
:::
```jsx=
// components/projects/EditProject.js
import React, { Component } from 'react';
import axios from 'axios';
class EditProject extends Component {
state = {
title: this.props.theProject.title,
description: this.props.theProject.description
}
handleFormSubmit = (event) => {
event.preventDefault();
const title = this.state.title;
const description = this.state.description;
axios.put(`http://localhost:5000/api/projects/${this.props.theProject._id}`, { title, description })
.then( () => {
this.props.getTheProject();
// after submitting the form, redirect to '/projects'
this.props.history.push('/projects');
})
.catch(err => console.log(err))
}
handleChangeTitle = (event) => {
this.setState({title: event.target.value})
}
handleChangeDesc = (event) => {
this.setState({description: event.target.value})
}
render(){
return (
<div>
<hr />
<h3>Edit form</h3>
<form onSubmit={this.handleFormSubmit}>
<label>Title:</label>
<input type="text" name="title" value={this.state.title} onChange={e => this.handleChangeTitle(e)}/>
<label>Description:</label>
<textarea name="description" value={this.state.description} onChange={e => this.handleChangeDesc(e)} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
export default EditProject;
```
At this point we are very familiar with forms :fire: since we already successfully built *`<AddProject />`* component. As you can see this component is heavily depending on **props** that are being passed into this component and we will see just now where and why.
:::info lecture
Explication du code :
- controled-components pour les champs du formulaire
- les state sont initialisés à l'aide de `props.theProject` qu'il va donc nous falloir passer au composant
```jsx=9
state = {
title: this.props.theProject.title,
description: this.props.theProject.description
}
```
- call API `update` réalisé lors du `'submit'`
- On remarque un appel à `this.props.history.push` pour effectuer une redirection après le call API de màj du projet :
```jsx=25
// after submitting the form, redirect to '/projects'
this.props.history.push('/projects');
```
:::
---
So let's see now, where we should render this component. We can do that here inside `<ProjectDetails />` component. Really it's the matter of your preference - if you want you can have the form rendered in the separate page as well. We will proceed with setting the edit/update functionality inside details component.
:::info lecture
Où allons-nous rendre notre nouveau composant `<EditProject />` ? A l'intérieur de `<ProjectDetails>` :
:::
```jsx=
// components/projects/ProjectDetails.js
...
// 👇
import EditProject from './EditProject';
class ProjectDetails extends Component {
...
// 👇
renderEditForm = () => {
if(!this.state.title){
this.getSingleProject();
} else {
// {...props} => so we can have 'this.props.history' in Edit.js
// ^
// |
return <EditProject theProject={this.state} getTheProject={this.getSingleProject} {...this.props} />
}
}
render(){
return (
<div>
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
<div>{this.renderEditForm()} </div> // 👈
<Link to={'/projects'}>Back to projects</Link>
</div>
)
}
}
```
:::info lecture
Explications:
- On passe en prop `theProject`, le projet issu des states:
```jsx=19
return <EditProject theProject={this.state}
```
- On passe également toutes les autres props afin notamment de disposer de `this.props.history` pour la redirection.
```jsx=19
{...this.props} />
```
:::
:::info
`renderEditForm()` method is being called inside the *render()* method and what it does is basically this: checks if `this.state` has any properties (we picked *title*), and if that's true, it's invoking the `getSingleProject()` method which gets the project object from our API and sets it to the state of the component. On the next execution of `renderEditForm()`, it's rendering `<EditProject />` component with `props` passed down to itself. Here we can see that what's being passed is *theProject* which is really THE project we are seeing on this details page and we are passing `getSingleProject()` method as `getTheProject` prop. Now if you look back to our `<EditProject />` component, everything makes more sense, right? :sunglasses:
:::
---
:::info lecture
<span style="font-size:500%;">5️⃣</span>
Pour finir un bouton delete dans `<ProjectDetail>`
:::
We are almost in the end of the first part of our app. Let's just add `delete` functionality so we can have full `CRUD` done. :muscle:
Inside `<ProjectDetails />` component, we will add `deleteProject()` method that will do axios call to the backend and *delete* route, and we will have *button* that will trigger this method's execution.
Our full `<ProjectDetails />` component at the end looks like this:
```jsx
// components/projects/ProjectDetails.js
...
class ProjectDetails extends Component {
...
// 👇 DELETE action:
deleteProject = () => {
const { params } = this.props.match;
axios.delete(`http://localhost:5000/api/projects/${params.id}`)
.then(response =>{
this.props.history.push('/projects'); // !!!
})
.catch((err)=>{
console.log(err)
})
}
render(){
return(
<div>
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
<div>{this.renderEditForm()} </div>
{/* 👇 */}
<button onClick={() => this.deleteProject()}>Delete project</button>
<br/>
<Link to={'/projects'}>Back to projects</Link>
</div>
)
}
}
export default ProjectDetails;
```
:::info lecture
Remarquons le redirect `this.props.history.push('/projects')` à la liste des projets une fois le delete fait.
:::
:::success lecture
WOW, nous en avons fini avec nos projets 🙌
Passons aux tâches...
:::
Projects done!!! Full CRUD works, so let's move to the **tasks** part. Sometimes... well in the most times, we will have the situation of having the need to nest components inside each other. So let's do it - our tasks belong to projects, so we will have to nest them inside the project component.
### Tasks
To start this working on this part of the app, let's first create `components/tasks` folder and our first task related component - `AddTask.js` and add the following code:
:::info lecture
Créons un nouveau composant `<AddTask>` :
```shell
mkdir src/components/tasks
touch src/components/tasks/AddTask.js
```
:::
```jsx=
// components/tasks/AddTask.js
import React, { Component } from 'react';
import axios from 'axios';
class AddTask extends Component {
constructor(props){
super(props); // will help us to toggle add task form
// |
this.state = { title: "", description: "", isShowing: false };
}
handleFormSubmit = (event) => {
event.preventDefault();
const title = this.state.title;
const description = this.state.description;
const projectID = this.props.theProject._id; // <== we need to know to which project the created task belong, so we need to get its 'id'
// it has to be the 'id' because we are referencing project
// by its id in the task model on the server side ( project: {type: Schema.Types.ObjectId, ref: 'Project'})
// { title, description, projectID } => this is 'req.body' that will be received on the server side in this route,
// so the names have to match
axios.post("http://localhost:5000/api/tasks", { title, description, projectID })
.then( () => {
// after submitting the form, retrieve project one more time so the new task is displayed as well
// |
this.props.getTheProject();
this.setState({title: "", description: ""});
})
.catch( error => console.log(error) )
}
handleChange = (event) => {
const {name, value} = event.target;
this.setState({[name]: value});
}
toggleForm = () => {
if(!this.state.isShowing){
this.setState({isShowing: true});
} else {
this.setState({isShowing: false});
}
}
showAddTaskForm = () => {
if(this.state.isShowing){
return(
<div>
<h3>Add Task</h3>
<form onSubmit={this.handleFormSubmit}>
<label>Title:</label>
<input type="text" name="title" value={this.state.title} onChange={ e => this.handleChange(e)}/>
<label>Description:</label>
<textarea name="description" value={this.state.description} onChange={ e => this.handleChange(e)} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
render(){
return(
<div>
<hr />
<button onClick={() => this.toggleForm()}> Add task </button>
{ this.showAddTaskForm() }
</div>
)
}
}
export default AddTask;
```
:::info lecture
- L17, nous allons avoir besoin de l'id du projet pour savoir sur quel projet ajouter la tache `this.props.theProject._id`
:::
Now when we've created the `AddTask` component, we have to put it somewhere in our app so the user can see it. Let's do it on the *project details page*.
```jsx
// components/projects/ProjectDetails.js
...
// 👨🏫 import the component:
import AddTask from '../tasks/AddTask';
class ProjectDetails extends Component {
...
// 👨🏫
renderAddTaskForm = () => {
if(!this.state.title){
this.getSingleProject();
} else {
// pass the project and method getSingleProject() as a props down to AddTask component
return <AddTask theProject={this.state} getTheProject={this.getSingleProject} />
}
}
render(){
return(
<div>
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
{/* 👨🏫 */}
{/* show the task heading only if there are tasks */}
{ this.state.tasks && this.state.tasks.length > 0 && <h3>Tasks </h3> }
{/* map through the array of tasks and... */}
{ this.state.tasks && this.state.tasks.map((task, index) => {
return(
<div key={ index }>
{/* ... make each task's title a link that goes to the task details page */}
<Link to={`/projects/${this.state._id}/tasks/${task._id}`}>
{ task.title }
</Link>
</div>
)
}) }
{/* END of 👨🏫 */}
<div>{this.renderEditForm()} </div>
<button onClick={() => this.deleteProject()}>Delete project</button> {/* <== !!! */}
<br/>
{/* 👨🏫 */}
<div>{this.renderAddTaskForm()} </div>
<br/><br/><br/><br/><br/>
<Link to={'/projects'}>Back to projects</Link>
</div>
)
}
}
export default ProjectDetails;
```
---
:::danger lecture
on passe
:::
Also, just for fun, let's list the tasks under each project title on the main, `/projects` page as well. There user can't click on them, that functionality is given on the project details page already.
```jsx
// components/projects/ProjectList.js
...
class ProjectList extends Component {
...
render(){
return(
<div>
<div style={{width: '60%', float:"left"}}>
{ this.state.listOfProjects.map( project => {
return (
<div key={project._id}>
<Link to={`/projects/${project._id}`}>
<h3>{project.title}</h3>
</Link>
{/* 🥁 added so the tasks can be displayed: 🥁 */}
<ul>
{ project.tasks.map((task, index) => {
return <li key={index}>{task.title}</li>
}) }
</ul>
{/* <p style={{maxWidth: '400px'}} >{project.description} </p> */}
</div>
)})
}
</div>
<div style={{width: '40%', float:"right"}}>
<AddProject getData={() => this.getAllProjects()}/> {/* <== !!! */}
</div>
</div>
)
}
}
export default ProjectList;
```
---
:::success lecture
Plus qu'un composant de détail d'une tâche `<TaskDetails>` :
:::
We will finish up the lesson with displaying the task details page so let's create `components/tasks/TaskDetails.js` file and add the following code snippet:
:::info lecture
```shell
touch src/components/tasks/TaskDetails.js
```
:::
```jsx
// components/tasks/TaskDetails.js
import React, { Component } from 'react';
import axios from 'axios';
class TaskDetails extends Component {
constructor(props){
super(props);
this.state = {};
}
componentDidMount(){
this.getTheTask();
}
getTheTask = () => {
const { params } = this.props.match;
axios.get(`http://localhost:5000/api/projects/${params.id}/tasks/${params.taskId}`)
.then( responseFromApi =>{
const theTask = responseFromApi.data;
this.setState(theTask);
})
.catch((err)=>{
console.log(err)
})
}
render(){
return(
<div>
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
</div>
)
}
}
export default TaskDetails;
```
And now we have to "tell" our app when to render this component so let's open `App.js`, import the `TaskDetails` component and add the following route in between the *<Switch>* tag as the third route:
```jsx
// App.js
...
// 👨🏫
import TaskDetails from './components/tasks/TaskDetails'; // <== import the TaskDetails component
class App extends Component {
render() {
return (
<div className="App">
<Navbar />
<Switch>
<Route exact path="/projects" component={ProjectList}/>
<Route exact path="/projects/:id" component={ProjectDetails} />
{/* 👨🏫 added to display task details page: */}
<Route exact path="/projects/:id/tasks/:taskId" component={TaskDetails} /> {/* <== !!! */}
</Switch>
</div>
);
}
}
export default App;
```
:::success lecture
🏁
:::
We leave now up to you to add edit and delete functionality on top of tasks. For the adventurous ones, try adding "done" property for the completed tasks.
## Summary
We learned how to integrate our frontend application developed on React with our NodeJS/Express application on the backend. We covered on a lot of places passing the props in the components as well as lifting the state up. Our next assignment is working on auth functionalities.
## Extra Resources
- [Axios](https://github.com/axios/axios)
- [Using Axios with React](https://alligator.io/react/axios-react/)