![logo_ironhack_blue 7](https://user-images.githubusercontent.com/23629340/40541063-a07a0a8a-601a-11e8-91b5-2f13e4e6b441.png) # 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 the usual CLI command. Run this command **inside** the already existing project folder. ```bash # Note: the folder needs to be called exactly `client` to match # with the nodemon ignore configuration from the previous lesson $ create-react-app client ``` :::info If you use `create-react-app` inside an already existing `git` repository it will not create its own. If it ever mistakenly created a git repo inside a subfolder of your actual project folder you can simply remove it by using `$ sudo rm -r client/.git`. ::: Now we can start the server by running `npm run dev` from the `react-day4-server-starter` folder (project folder) directly. And we start the client by running `npm start` from the `react-day4-server-starter/client` folder. Our server side is running on port `5555`. The React client runs on port `3000` by default. ### React proxy setup Edit `client/package.json` and add the following line as a property to the config object: ```json "proxy": "http://localhost:5555" ``` This tells React to send all requests that we set up in this lesson to our backend (which is running on port `5555`). ### Basic Structure We won't dig into the routes just yet, but for now we will create two components, one to create projects and another one to display projects, and we will take it from there. We will be using `axios` to make calls to our server side and for that purpose, we need to install it (run the following line from inside the `client` folder): ``` bash $ npm install axios ``` :::info Remember to import `axios` on top of each component that is communicating with the backend. ```jsx import axios from 'axios'; ``` ::: Since we know that we will have some routes, let's install `react-router-dom` the same way at this point: ``` bash $ npm install react-router-dom ``` So let's start off: inside the `src` folder create a `components` folder and in it create a `projects` folder. Inside the `projects` folder, create files `AddProject.js` and `ProjectList.js`. You should end up with this structure: ``` src ├── components └── projects └── AddProject.js └── ProjectList.js ``` We will start with building the `<AddProject />` component. This component will display a form and will handle its submission. By that we mean it uses `axios` to reach a backend route and deliver some data. So let's do this: ```jsx // components/projects/AddProject.js import React, { Component } from 'react'; import axios from 'axios'; class AddProject extends Component { constructor(props){ super(props); this.state = { title: "", description: "" }; } handleFormSubmit = (event) => { event.preventDefault(); const title = this.state.title; const description = this.state.description; axios.post("/api/projects", { title, description }) .then( () => { // this.props.getData(); 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 Leave `this.props.getData()` commented out for now, soon we will need it. ::: Most of the code looks very familiar right? That's because we already covered basics of using forms in React. Let's import the `<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. Make sure your server is running on port `5555` at the same time and try to add a new project. Check your database and you should see the new project in the `projects` collection. 🎯 So now that we have one project in our database, let's proceed to creating a `<ProjectList />` component. Inside this component we will use `axios` to get all the projects from backend. ```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 { constructor(){ super(); this.state = { listOfProjects: [] }; } getAllProjects = () =>{ axios.get("/api/projects") .then(responseFromApi => { this.setState({ listOfProjects: responseFromApi.data }) }) } 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 Let's see what is interesting here: - we use the `componentDidMount()` lifecycle method to fetch data from the API (this is very much standard practice). - we use `map()` to list the projects (don't forget to pass each element the database ID as the key with `key={project._id}`); - we use the `<Link />` component from the `react-router-dom` library to dynamically change the URL and in our case, to go to a project's detail page (that's our next thing to work on). - we see that the `<AddProject />` component will be actually nested inside `<ProjectList />` so let's go and remove it from `App.js` and also let's un-comment `this.props.getData()` inside `<AddProject />`. - we commented out the project description line since we will be building a details page and the description will be shown there. ::: 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, in `index.js`, where we render the `<App />`component. ```jsx // index.js import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import { BrowserRouter as Router } from 'react-router-dom'; ReactDOM.render( <Router> <App /> </Router>, document.getElementById('root') ); serviceWorker.unregister(); ``` After importing `BrowserRouter`, we use it by wrapping up the `<App />` tag. At this moment, our *App.js* looks like this: ```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; ``` Let's add another file in the `components/projects` folder, a `ProjectDetails.js` and add just these couple of lines of code for now: ```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 { constructor(props){ super(props); this.state = {}; } render(){ return <div>Welcome to project details page!</div> } } export default ProjectDetails; ``` Moving forward, inside the `components` folder let's create another folder `navbar` and inside this folder let's create `Navbar.js`. So now, 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: ```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; ``` 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. For now we can: create a new project, display all the projects and by clicking on a project's title, we are redirected to the *project details* page which for now doesn't have much code. Let's add some! ```jsx // components/projects/ProjectDetails.js import React, { Component } from 'react'; import axios from 'axios'; import { Link } from 'react-router-dom'; class ProjectDetails extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount(){ this.getSingleProject(); } getSingleProject = () => { const { params } = this.props.match; axios.get(`/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 straight forward: `componentDidMount()` is executing `getSingleProject()` which initially communicates with our backend route through the axios call. If everything is successful, we are updating the state (using nothing but *setState()*) and setting it to include the project object we got from our API. In the *render()* part, we are accessing the project's properties through `this.state.title` and `this.state.description`. Next challenge is displaying an *edit form* and *updating* the project. First of all, we will create a new file inside the `components/projects` folder: `EditProject.js`. This component will include the form that will use *onSubmit* to make changes to a specific project. ```jsx // components/projects/EditProject.js import React, { Component } from 'react'; import axios from 'axios'; class EditProject extends Component { constructor(props){ super(props); this.state = { title: this.props.theProject.title, description: this.props.theProject.description } } handleFormSubmit = (event) => { const title = this.state.title; const description = this.state.description; event.preventDefault(); axios.put(`/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( error => console.log(error) ) } 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 should be pretty familiar with forms :fire: since we already successfully built the *<AddProject />* component previously. As you can see this component depends heavily on **props** that are being passed in and we will see just now where and why. So let's see now where we should render this component. We can do that here inside our `<ProjectDetails />` component. Really it's a matter of 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 the details component. ```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> ) } } export default ProjectDetails; ``` :::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 the`<EditProject />` component with `props` passed down to it. 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 the `getSingleProject()` method as a `getTheProject` prop. Now if you look back to our `<EditProject />` component, everything makes more sense, right? :sunglasses: ::: 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 a `deleteProject()` method that will do an axios call to the backend *delete* route, and we will have a *button* that will trigger this method's execution. Our full `<ProjectDetails />` component at the end looks like this: ```jsx // components/projects/ProjectDetails.js import React, { Component } from 'react'; import axios from 'axios'; import { Link } from 'react-router-dom'; import EditProject from './EditProject'; class ProjectDetails extends Component { constructor(props){ super(props); this.state = {}; } componentDidMount(){ this.getSingleProject(); } getSingleProject = () => { const { params } = this.props.match; axios.get(`/api/projects/${params.id}`) .then( responseFromApi =>{ const theProject = responseFromApi.data; this.setState(theProject); }) .catch((err)=>{ console.log(err) }) } 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} /> } } // DELETE PROJECT: deleteProject = () => { const { params } = this.props.match; axios.delete(`/api/projects/${params.id}`) .then( () =>{ 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; ``` Projects done!!! Full CRUD works, so let's move to the **tasks** part. Sometimes... well most times, we will have the situation of having 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. To start working on this part of the app, let's first create a `components/tasks` folder and our first task related component - `AddTask.js` and add the following code: ```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("/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; ``` Now after we've created the `AddTask` component, we have to put it somewhere in our app so the user can see it. Let's add it to the *project details page*. ```jsx // components/projects/ProjectDetails.js ... // import the component: import AddTask from '../tasks/AddTask'; class ProjectDetails extends Component { // after the delete project part add the following code: 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={`/tasks/${task._id}`}> { task.title } </Link> </div> ) }) } <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; ``` 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 though, 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; ``` We will finish up the lesson with displaying the task details page so let's create a `components/tasks/TaskDetails.js` file and add the following code: ```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(`/api/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 where 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="/tasks/:taskId" component={TaskDetails} /> {/* <== !!! */} </Switch> </div> ); } } export default App; ``` We leave it up to you to add edit and delete functionality for tasks as well. For the adventurous ones, also try adding a "done" property for completed tasks. ## Summary We learned how to integrate our frontend application developed on React with our NodeJS/Express application on the backend. We covered passing props into components as well as lifting state up. Our next assignment will be to work on authentication functionalities. ## Extra Resources - [Axios](https://github.com/axios/axios) - [Using Axios with React](https://alligator.io/react/axios-react/)