Section 1

CRUD App with Mongoose - Delete Edit and Update

arthur_node_jsx_diagram_photoshopped

mvc-meme

Review Questions

  • ❓ In your own words, describe the use case for Mongoose (what is it's purpose and when might you choose to use it?).
  • ❓ A Mongoose _ is compiled into a Mongoose Model.
  • ❓ We use a Mongoose _ to perform CRUD operations on a MongoDB Collection..

Describe REST and list the various routes

  • REST stands for Representational state transfer
  • It's just a set of principles that describe how networked resources are accessed and manipulated
  • Even though Mongoose Has Many Different Method in a basic REST implementation we restrict the methods to the below table
URL HTTP Verb Action Used For Mongoose Model Function
/things/ GET index Displaying a list of all things .find
/things/new GET new Display HTML form for creating a new thing N/A
/things POST create Create a new thing .create
/things/:id GET show Display a specific thing .findById
/things/:id/edit GET edit Return an HTML form for editing a thing .findById
/things/:id PATCH/PUT update Update a specific thing .findByIdAndUpdate
/things/:id DELETE destroy Delete a specific thing .findByIdAndDelete

Beginning with the End in Mind:

Review Adding a Layout:

  1. Create a Layout Folder
  2. Make Default.jsx
  3. Use The Default.jsx on our pages

Deletion:

  1. Create a Delete Button for our new and improved Index.jsx page
  2. Create a DELETE Route
  3. Have the Delete Button send a DELETE request to the server
  4. Make the DELETE Route Delete the Model from MongoDB

Edit/Update:

  1. Create a link to the edit route
  2. Create an edit route
  3. Create an PUT route
  4. Have the edit page send a PUT request
  5. Make the PUT Route Update the Model in MongoDB
  6. Make the PUT Route Redirect Back to the Index Page

Deployment:

  1. Make a new github repository
  2. set up environmental variables
  3. Remove node_modules
  4. Get started with deployment services.
  5. Create app.
  6. Attach MongoDB Atlas
  7. Update code.
  8. Push git to deployment.

What is a Layout

Sometimes you will have design elements that you want to repeat from page to page, It can get very repetitive to repeat these items from page to page.

You may have the same header, the same footer, the same sidebar etc every page you navigate to.

React lets us use Components in our JSX the same way you use traditional html elements, but we get to determine the design and functionality of the Components.

A different way of Seperating Concerns ( Component Driven Development )

components

What will we do?

  • We will make a Layout Component that has our default header and footer code
  • We will set up this Layout Component in such a way that it can be imported into our New, Show, Index and Edit Views
  • We will make it a Higher Order Component which means it can accept other components as children simmilar to how divs work in HTML
<div>
  Children of the div go here
</div>  

Our JSX will look like this

<Layout>
  Children of the Layout go here   
</Layout>  

We will also use PROPS

Props

Props allows a component to receive data from its parent component.

Some rules

  • Props can only be sent from a parent to a child
  • If the parent needs data from the child it should send a function as a prop then the child can pass its data to the function as an argument.
  • Anything can be sent as a prop, include JSX
  • Express React Views as you know takes the options object from res.render and merges that data with the components PROPS

Our JSX will look like this

<Layout someProp="information">
  Children of the Layout go here   
</Layout>  

Examples

<Layout css="/css/styles.css">
  Children of the Layout go here   
</Layout>  

or

<Layout title="Index Page">
  Children of the Layout go here   
</Layout>  

or

<Layout fruit={this.props.fruit}>
  Children of the Layout go here   
</Layout>  

These two examples are an example of how one of the Views might pass data down to Layout Component

In the example that we will create below we will discuss how to use the props

Add a Layout

Use the code from last class and lets continue to update fruits

From your terminal

cd views

Then

mkdir layout && touch layout/Default.jsx

Then

code layout/Default.jsx

Open Up your Default.jsx and type up the following code

Simply pass the relevant props to a layout component.

views/layouts/Default.jsx:

const React = require('react');

class DefaultLayout extends React.Component {
  render() {
    return (
      <html>
        <head><title>{this.props.title}</title></head>
        <body>
        <h1>{this.props.title}</h1>
        {this.props.children}
        </body>
      </html>
    );
  }
}

module.exports = DefaultLayout;

views/Index.jsx:

const React = require('react');
const DefaultLayout = require('./layouts/default');

class Index extends React.Component {
  render(){
    const fruits = this.props.fruits;
    return (
      <DefaultLayout title={"Fruits Index Page"}>
        <nav>
          <a href="/fruits/new">Create a New Fruit</a>
        </nav>
        <ul>
          {
            fruits.map((fruit)=>{
              return (
                <li key={fruit._id}>
                  The <a href={`/fruits/${fruit._id}`}>{fruit.name}</a>
                  {' '}is {fruit.color} <br/>
                  {
                    fruit.readyToEat?
                    '  It is ready to eat':
                    '  It is NASTY!!!!!!'
                  }
                </li>
              )
            })
          }
        </ul>
      </DefaultLayout>
    )
  }
}

module.exports = Index;

Where does my data come from?

All "locals" are exposed to your view in this.props. These should work identically to other view engines with the exception of how they are exposed. Using this.props follows the pattern of passing data into a React component, which is why we do it that way. Remember, as with other engines, rendering is synchronous. If you have database access or other async operations, they should be done in your routes. For example our Mongoose Operations are Asynchronous

JSX Syntax Quirks

  1. className instead of class
  2. props (always come from Outside the component)
  3. {} to embed Javascript
  4. {{}} for inline styles
  5. Writing CSS in CamelCase
  6. Conditional rendering
  7. Everything Must Close
  8. Looping with map or map & filter
  9. Anything can be dynamic

Practice

Note: we will use this layout below in our New , Show and Edit Page so just hold tight. Make sure you save this file.

Create a Delete Button

In your Index.jsx file you will see the map function that we made previously we are going to swap out that map method with our updated map method. We will add a delete button next to each fruit. If this button is clicked we will delete the fruit

      {this.props.fruits.map((fruit,i) => {
                  return <li key={i}>
                      <a href={`/fruits/${fruit.id}`}>{fruit.name}</a>
                      is { fruit.readyToEat? <span>It is ready to eat</span>: <span> It is not ready to eat </span>}
                      {/* Your Delete Form Goes Here  It's incomplete we will fix below*/}
                      <form>
                          <input type="submit" value="DELETE"/>
                      </form>
                  </li>
              })}

Create a Delete Route : We will go into our server.js and create a delete route.

For right now we are not really deleting anything we are just going to send the word deleting to the browser.

app.delete('/fruits/:id', (req, res)=>{
    res.send('deleting...');
});

Have the Delete Button send a DELETE request to the server

When we click "DELETE" on our index page (index.jsx), the form needs to make a DELETE request to our DELETE route.

The problem is that forms can't make DELETE requests. Only POST and GET. We can fake this, though. First we need to install an npm package called method-override

npm i method-override

Now, in our server.js file, add:

//include the method-override package place this where you instructor places it
const methodOverride = require('method-override');
//...
//after app has been defined
//use methodOverride.  We'll be adding a query parameter to our delete form named _method
app.use(methodOverride('_method'));

Now we need to go back and set up our delete form to send a DELETE request to the appropriate route, so we will be returning to our views/Index.jsx file and changing the opening form tag.

  <form action={`/fruits/${fruit._id}?_method=DELETE`} method="POST">

Make the Delete Route Delete the Fruit from MongoDB

Also, have it redirect back to the fruits index page when deletion is complete. This must be done in our server.js file in the delete route that we created a few thumbs up ago.

app.delete('/fruits/:id', (req, res)=>{
    Fruit.findByIdAndRemove(req.params.id, (err, data)=>{
        res.redirect('/fruits');//redirect back to fruits index
    });
});

In your Index.jsx file:

<a href={`/fruits/${fruit._id}/edit`}>Edit This Fruit</a>

Create an edit route/page

First the lets create an edit route in our server.js file do this below the delete route we created earlier:

app.get('/fruits/:id/edit', (req, res)=>{
    Fruit.findById(req.params.id, (err, foundFruit)=>{ //find the fruit
      if(!err){
        res.render(
    		  'Edit',
    		{
    			fruit: foundFruit //pass in the found fruit so we can prefill the form
    		}
    	);
    } else {
      res.send({ msg: err.message })
    }
    });
});

Now the lets make the JSX file by using the command line to touch a Edit.jsx file in our views folder, then opening it in VS Code:

const React = require('react');
// As you can see we are using the app layout
const DefaultLayout = require('./layout/Default.jsx')

class Edit extends React.Component{
  render() {
    return (
      <DefaultLayout title="Edit Page">      
     {/* See the Layout takes in a prop called Title and we pass Edit Page to it  note: comments can't go first or last in  jsx return*/}
          {/* form is not complete we will do that below*/}
      <form>
          Name: <input type="text" name="name" defaultValue={this.props.fruit.name}/><br/>
          Color: <input type="text" name="color"  defaultValue={this.props.fruit.color}/><br/>
          Is Ready To Eat:
              { this.props.fruit.readyToEat? <input type="checkbox" name="readyToEat" defaultChecked />: <input type="checkbox" name="readyToEat"/> }
          <br/>
          <input type="submit" value="Submit Changes"/>
      </form>
      </DefaultLayout>
    )
  }
}
module.exports= Edit;

Create an PUT route

Make the PUT Route Update the Fruit in MongoDB and redirect to that fruit's show page

app.put('/fruits/:id', (req, res)=>{
    if(req.body.readyToEat === 'on'){
        req.body.readyToEat = true;
    } else {
        req.body.readyToEat = false;
    }
    Fruit.findByIdAndUpdate(req.params.id, req.body, (err, updatedFruit)=>{
       console.log(updatedFruit)
        res.redirect(`/fruits/${req.params.id}`);
    });
});

Have the edit page send a PUT request

In the Edit.jsx find the opening form tag and add the code below you should only have one opening form tag after you finish this.

<form action={`/fruits/${this.props.fruit._id}?_method=PUT`} method="POST">

Potential Bugs : Did you remove the other form tag if you copied and pasted? , did you miss adding method override?

Create a Seed Route

Sometimes you might need to add data to your database for testing purposes. You can do so like this:

app.get('/fruits/seed', (req, res)=>{
    Fruit.create([
        {
            name:'grapefruit',
            color:'pink',
            readyToEat:true
        },
        {
            name:'grape',
            color:'purple',
            readyToEat:false
        },
        {
            name:'avocado',
            color:'green',
            readyToEat:true
        }
    ], (err, data)=>{
        res.redirect('/fruits');
    })
});

Review Questions

  • ❓ In your own words, describe the use case for methodOverride
  • ❓ In your own words, describe the use case for a SEED Route
  • ❓ In your own words, describe the use case for a Layout Component. Why is it DRY?

Bonus

We used Error First Callbacks in our examples... how might we use .then and .catch instead....

Copyright © Per Scholas 2023