Benton Westergaard
5 min readOct 4, 2021

--

Lockdown (4)

Right, so as I slowly but deliberately make my way towards auth, I’m going to push on and add full CRUD (Create, Read, Update, Delete/Destroy) functionality to my app. I can already create a new item — a bicycle in my collection- so now I need to add the ability to modify items/bicycles in my collection, as well as delete them. Both actions will require a route on the backend, a function with the React frontend to interact with the backend, as well as some simple CSS to add edit and delete buttons to the bicycle/item items.

Link to Lockdown (3) blog

Link to github repo

Adding delete/destroy functionality is the easiest, so I’m going to tackle this first. Like a warm-up before diving into update.

First step is to create the route on the backend on the bicyles_controller. In summary, I need to find the bicycle I want to delete by it’s ID, and then remove it.

def destroy
@bicycle = Bicycle.find(params[:id])
@bicycle.destroy
render status: :no_content
end

Now, I need to accommodate this on the front end. First, in the BicycleItem.js file, I’m going to add a button. It won’t do anything, but at least I’ll know it’s showing up on the page:

<li className="bicycle-item">
<h4>Type: {usage}</h4>
<h4>Frame Material: {frame_material}</h4>
<h4>Made by: {manufacturer}</h4>
<h4>Speeds: {speeds}</h4>
<button className="delete-button">DELETE</button>
</li>

Then in App.css, I reference the new delete button and add some initial styling:

.delete-button {
background-color: red;
color: white;
}

There is now a red ‘delete’ button on the page, although it doesn’t do anything. Now, before making it ‘work,’ I want to explain what my intention is: when a user clicks the button, the item will not only be removed from the bicycle collection on the backend, but it will also disappear from the collection on the home page (without the user having to refresh the page). Now, before connecting a new delete function on the front end to the new delete route on the backend, I want, ideally, to have the action of clicking the delete button make the associated bicycle item disappear. In other words, I want to remove the bicycle item in question from state, so that it isn’t included when the array is looped over. Because I need to modify state, I’ll start in App.js to add my delete function:

deleteBicycle = (id) => {
let filtered = this.state.bicycles.filter(bicycle => bicycle.id !== id)
this.setState({
bicycles: filtered
})
}

What’s happening is, I am creating a new array (filtered), which is the bicycle collection, with each bicycle that is NOT the id of the bicycle for which the delete button was clicked. However, since I’m in App.js — two levels above the bicycle item — I need to work it down. So within the render method of App.js, I first send it down to BicycleContainer (without invoking it)…

render(){
return (
<div className="App">
<h1>Bicycle App</h1>
<BicycleForm addBicycle={this.addBicycle} />
<BicycleContainer deleteBicycle={this.deleteBicycle} bicycles={this.state.bicycles} />
</div>
);
}
}

It will now be a prop of BicycleContainer. I’ve already started destructuring in BicycleContainer, so I need to add in my next prop:

export default function BicycleContainer({bicycles, deleteBicycle}) 

And since each bicycle in the collection will have access to this function, I add it to my map. There is no “this,” because I’m using a functional component (rather than a class component). Also, “props” is not used either, because we destructured the prop deleteBicycle:

const showBicycles = () => {
return bicycles.map(bicycle => <BicycleItem key={bicycle.id} {...bicycle} deleteBicycle={deleteBicycle} />)
}

Now, what I want to do, is have the action of a user clicking the ‘delete’ button send the ID of the associated bicycle up to App.js, where it will be removed from the collection. So, in BicycleItem.js, I first add a onClick function to the delete button:

<button onClick={handleClick} className="delete-button" >DELETE</button>

Then I add the deleteBicycle prop to my export:

export default function BicycleItem({usage, frame_material, manufacturer, speeds, deleteBicycle}) {

And now write a function for my handleClick:

const handleClick = (e) => deleteBicycle(id)

Since I’ll need the prop of id for the handleClick, I’ll also add this to my destructured props:

export default function BicycleItem({id, usage, frame_material, manufacturer, speeds, deleteBicycle}) {

Now, to test this, when clicking the delete button for any bicycle, it should exit the page, which it does. Of course, it doesn’t persist, but it wouldn’t make sense to work on this until ensuring the onClick function is working the way it is supposed to work.

Connecting to the backend is easy, since I’ve already created the route. I just need to add a fetch call to my deleteBicycle function in App.js:

deleteBicycle = (id) => {
let filtered = this.state.bicycles.filter(bicycle => bicycle.id !== id)
this.setState({
bicycles: filtered
})
fetch(BASEURL + "/" + id, {method: "DELETE"} )
}

Now, to get started on adding an Update function. Just like before, I’m going to start on the backend by creating an Update action. Since the update action has the ‘find’ functionality in common with the destroy route, I’m going to create a private route to find the bicycle, and then call that in both the update and delete functions. I’ll use a before_action line at the top, so that the update function only runs for the update and delete routes. There is no need to use a find function when you’re creating a new bicycle, for example.

For simplicity, I’m also going to set up strong params.

class BicyclesController < ApplicationControllerbefore_action :find_bicycle, only: [:update, :destroy]def index
@bicycles = Bicycle.all
render json: @bicycles
end
def create
@bicycle = Bicycle.create(bicycle_params)
render json: @bicycle, status: :created
end
def destroy
# @bicycle = Bicycle.find(params[:id])
@bicycle.destroy
render status: :no_content
end
def update
# @bicycle = Bicycle.find(params[:id])
@bicycle.update(bicycle_params)
render json: @bicycle
end
privatedef find_bicycle
@bicycle = Bicycle.find(params[:id])
end
def bicycle_params
params.require(:bicycle).permit(:usage, :frame_material, :manufacturer, :speeds)
end
end

I’m going to create a separate blog for finishing the the update function because building it will be slightly more complex than I was first thinking. What I want to see, is is for users to be able to click a button (the easy part), which will make the createBicycle form appear, but which will 1) populate with the data for that bicycle, and 2) give users the ability to edit the existing data using the same form. I’ll need to use at least a couple conditional statements to make this happen, and I think it’s better to partition this process between two blogs.

So, to recap, for this blog, I’ve added delete functionality to the app (front and backend), and added edit routes to the backend. For the next blog in the series, I’ll actually add the edit button and make it persist. And eventually, we’ll get to auth:)

github

--

--