- Read Tutorial
- Watch Guide Video
Now pretty much everything that we're going to do in this guide is reinforcement learning, we have performed this task with our portfolio manager. If you come to the portfolio manager, I've cleaned out all of my demo portfolio items so that my homepage looks better.
But if you were to click on one of your trash icons, this would automatically delete the post and your portfolio manager would update state and it would only have your active portfolio items, we're gonna do the same thing with our blog. So we're going to have a link, I want to have a trash icon here on the right-hand side, and if the user is logged in, so if you're logged in then you should see this trash icon, you can click it and it'll automatically remove the blog. If a user is not logged in, then no trash icon appears, and so that is what we're gonna implement in this guide.
I'm gonna open up Visual Studio Code, and the very first thing that we need to do is we need to create a slightly different blogItem. So right now, if you scroll down into what's getting returned, you'll see that we have these blogRecords, and it's simply iterating over your state and it is saying okay, here is our BlogItem component. And that works perfectly fine, and that's the behavior we wanna keep for users that are not logged in.
But if you are logged in we want to create a slightly different value that gets returned. So what I'm gonna do is I'm going to add a conditional here, so I'm gonna say if and then this.props, and remember the blog we're passing in, the render prop from the app component, and we're passing in our loggedInStatus.
And so we're gonna say if the user is LOGGED_IN then I want to perform one type of task. I want to render a new BlogItem, one that has our special trash icon and the ability to delete, and if not, so if that's not the case I wanna have this else condition, and that's where we're going to return exactly what we're returning right now.
So let's create this wrapper div here I'm gonna say return and then in parens I'm going to create a div with a className of, we can just call it something like admin-blog-wrapper. And now inside of this div we're going to have our regular BlogItem, so nothing's going to change here, so you can just paste that in, but then we're also going to have our delete icon.
So you can add an a tag, and then we'll give a onClick handler. We haven't actually created this function yet, but we can say onClick and then this.handleDeleteClick, and then inside of here, for right now we'll say Delete, and then, later on, we're going to add our icon. So let's just hit save and then stub out this handleDeleteClick method.
blog.js
render() { const blogRecords = this.state.blogItems.map(blogItem => { if (this.props.loggedInStatus === "LOGGED_IN") { return ( <div className="admin-blog-wrapper"> <BlogItem key={blogItem.id} blogItem={blogItem} /> <a onClick={this.handleDeleteClick}>Delete</a> </div> ); } else { return <BlogItem key={blogItem.id} blogItem={blogItem} />; } });
So if you come up to the top to your constructor, add that in, say, this.handleDeleteCLick is equal to this.handleDeleteClick.bind and pass in this. And then you can just create this method. For right now we're not gonna pass an argument in, but as soon as we know this is working we are going to because we have to know which blog is being deleted. But for right now, let's just say console.log deleted and hit save.
this.handleDeleteClick = this.handleDeleteClick.bind(this); } handleDeleteClick() { console.log("deleted"); }
Now let's test this out. We're logged in and so we see this delete link, and if you open up the console and click Delete you'll see that's working.
Now if I log out and then return back to the blog, you'll see the delete link is not there.
So our authorization rules are working properly. Let's log back in, and then once we're logged in we can continue the build out of that function. So go to the blog. One thing you may have noticed, we now have this warning, this each child in an array or iterator should have a unique key prop.
And you might think, well we have that, right? If you come down, we didn't have that warning before because our BlogItem has the key. And you may think, well, our BlogItem has the key right here. But with the way that React works, the key prop needs to be at the outermost wrapper, the outermost element. So in this case, in our admin, it's no longer the BlogItem.
So we can take this key prop here, and we can slide it directly into the div, hit save, and now if you open this up, you'll see our warning is gone. So everything there is now working.
blog.js
render() { const blogRecords = this.state.blogItems.map(blogItem => { if (this.props.loggedInStatus === "LOGGED_IN") { return ( <div key={blogItem.id} className="admin-blog-wrapper"> <BlogItem blogItem={blogItem} /> <a onClick={this.handleDeleteClick}>Delete</a> </div> ); } else { return <BlogItem key={blogItem.id} blogItem={blogItem} />; } });
So let's continue this build out. So handleDeleteClick, that needs to take in an argument, it needs to take in the actual BlogItem that is going to be deleted. So we need to pass in an argument to handleDeleteClick, but as a reminder, with the way JavaScript works, if I were to simply, and do not do this, just watch this do not type this in.
If I were to say, BlogItem like this, what would happen is when the page renders, JavaScript would execute this automatically. It wouldn't wait for onClick, it would just process it because it would look at this code and anytime JavaScript sees a function with the parens at the end, it thinks you wanna run it. That would be a really bad thing.
So remember the way that we do this is we pass in an anonymous function, so we're gonna pass in parens and then a fat arrow function. So whenever you want to have a function that is what's called lazy loaded, which means that you do not want this process to run until a certain event occurs such as, in this case, clicking the link, then you can wrap it up like this.
blog.js
render() { const blogRecords = this.state.blogItems.map(blogItem => { if (this.props.loggedInStatus === "LOGGED_IN") { return ( <div key={blogItem.id} className="admin-blog-wrapper"> <BlogItem blogItem={blogItem} /> <a onClick={() => this.handleDeleteClick(blogItem)}>Delete</a> </div> ); } else { return <BlogItem key={blogItem.id} blogItem={blogItem} />; } });
So now that we have that, we can come up into our function of handleDeleteClick. It's gonna take in a blog argument, and we can test this out to make sure that it's working.
blog.js
handleDeleteClick(blog) {
console.log("deleted", blog);
}
Hit save and now if you come back to Google Chrome and click Delete you'll see that now we're actually getting the blog record itself.
And we do need that because the ID is what's gonna be passed to the API, because the API needs to know which blog is getting deleted, and we also need to know which blog we want to remove from our local state, so with this in place, that is all we need.
Now we can build out this handleDeleteClick function, and that's going to give us the ability to remove these items. Let's remove our console.log, and let's start adding it. Now you could copy and paste this pretty much from your portfolio manager, but it's always good to practice typing it out. So I'm going to call axios and let's delete this.
So we're gonna call the delete function in axios and now we need to pass in the string. Now this is one piece of information you don't have, which is the API URL, but you might be able to guess it because it's very similar to the portfolio item. I'm gonna call https://api.devcamp.space, and then from there portfolio/portfolio_blogs/ and then we need to pass in, and this is why we use back ticks for string literals, now we need to pass in the blog.id.
So this is the same blog.id we just saw in the console. Now, in order for this to work, because the API needs to know that you're authorized to delete this blog, as a second argument to delete we're going to pass in another object and say withCredentials true. And then at the very end of the parens we can just say then and then give our response.
For right now let's just console.log it, so console.log response from delete. And then let's take a look at what the response is, and also make sure it works. And then we want to catch any server errors, and for those same thing, we'll console.log delete blog error, and let's grab that error.
blog.js
handleDeleteClick(blog) { axios .delete( 'https://api.devcamp.space/portfolio/portfolio_blogs/${blog.id}', { withCredentials: true } ) .then(response => { console.log("res from delete", response); }) .catch(error => { console.log("delete blog error", error); }); }
Okay, so now we have everything in place, let's test this out and let's communicate with the API. And you can pick any blogs, but one thing to keep in mind is if you delete one of these it is gone forever. It's not archived or anything, so make sure you only do this with test blogs. If you've been writing actual blog posts, do not click this or else it's gone and it's not coming back. I do not have an archiving feature or anything like that in place for DevCamp Space.
So clear out the console, click Delete, and there we go. Here's our response from delete. You can see, it doesn't return any data. It gives a status return of 204 and that is the universal http code for your delete was handled successfully.
The reason why no data is returned is because you deleted the record, there's no data to send back. Thus, it's very standard for all that is gonna be returned is the actual status code itself.
So with that, what we can do now is we can update our state. Because if you noticed, I clicked delete, but it didn't get removed from the page. What we need to do is now actually update our state. So I'll say this.setState, and then inside of here this is where we're going to have our filter function. Very similar to the filter function, nearly identical actually, to the filter function we implemented with our portfolio items.
So with this we have our blogItems inside of state. And we're going to set this equal to this.state.blogItems.filter, and we're going to have access to each one of the blogItems in our state. Remember, that's how a filter works. Filter's very similar to map, it simply gives you the ability to pick and choose which values you wanna add into the array that you're creating.
So I'm gonna say for this blogItem, if the blogItem is equal to the blog.id, which if you remember, this is the blog.id that's being passed in. It's a blog we want to delete. So I'm gonna say return and then blog.id if it is not equal to, and one thing, I have kind of a special font on my system. That's the reason why you see icons like this or why my triple equals looks weird or why the fat arrow looks weird. It's a font, it's a coding font that simply adds icons like that. So if you're wondering certain icons look weird when I'm typing 'em, that is why.
So I'm gonna say return if the blog.id is not equal to the blogItem.id. So all we're doing here, just kind of as a review, is we're iterating through each one of the blogItems, and we're saying if the blogItem we're currently looking at, so let's say that we have this full list like we do right here, and it's gonna look at testing after update feature, then it's gonna look at rich text image.
It's gonna look at each one of these in state. And then it's gonna compare that with the ID of whatever we clicked on for delete. If it does not line up, it'll keep it, if it does line up it's going to just be left out, which is exactly the behavior we want.
So hit save and that is all that we need to do. You also can, if you want, just because you don't really usually like to keep a response and not do anything with it, you can just return the response data, which we already know is empty. But that way we're at least using it and the function is returning something.
blog.js
handleDeleteClick(blog) { axios .delete( `https://api.devcamp.space/portfolio/portfolio_blogs/${blog.id}`, { withCredentials: true } ) .then(response => { this.setState({ blogItems: this.state.blogItems.filter(blogItem => { return blog.id !== blogItem.id; }) }); return response.data; }) .catch(error => { console.log("delete blog error", error); }); }
So let's test this out, hit refresh, and now if you click delete on any of these, you can see it actually gets removed, and it's updating state.
So that is all working perfectly, all we have to do now is add a little bit of formatting, and this feature will be totally done. So let's open this up and let's see how we can style this. If you come all the way down here, we have this admin-blog-wrapper, and if you open up your blog.scss file, right inside a blog-container this is where we can add these styles. And it's gonna be relatively straightforward because we're just creating a basic wrapper with two components.
One of the components is this div, well actually the entire thing is a div, but then inside of it is the blogItem and then the link. So what we can do is we can use flexbox, and just say we want to have flex with space between. That is the justified property so that if you have two elements, one will be all the way to the left, the other one will be all the way to the right, which is exactly what we're looking for, so that's convenient.
So I'm going to, for admin-blog-wrapper say display flex, justify-content is going to be space-between. And then for there let's also align the items so that they're aligned vertically. So we'll say align center, hit save, let's see if this is working there we go.
So this looks really nice and now we have all of our Delete what will soon be icons on the right-hand side and that looks good. Let's also add a little bit of margin, so we're going to add an a tag inside of that blog wrapper, and let's just add some margin-left. For that we can just say 15 pixels, and you may have noticed, when I hover over here it's treating it like text. So let's also add a cursor, hit save and now that is looking really nice.
blog.scss
.admin-blog-wrapper { display: flex; justify-content: space-between; align-items: center; a { margin-left: 15px; cursor: pointer; } }
So I like it if you hit refresh here, you'll see that is looking great.
Last thing is, let's just add our icon and we will be done with this feature.
So now instead of this Delete text we're going to use our FontAwesome library. So call the FontAwesomeIcon component and this is going to be a icon prop of trash, hit save and come back here. There you go, we have our little trash recycle bins, just like we had with the portfolio manager, and now if you click on any of these then you'll see these are working, these are removing from the actual server. So only do this with blog posts that you actually don't care about.
So that is looking perfect, I think we are completely done with this feature.
Now in the next guide we have one very small fix, now it's not really a fix, we're just going to add some styles to our form for logging in, then we're gonna be done, and this site is gonna be ready to deploy.