Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).

Deleting Items

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our app is looking great! But I know, we're missing one big piece: actually making AJAX requests so that all of this saves to the server. That is coming very soon. But, we have one more piece of homework first: adding the ability to delete rep logs.

Open up RepLogList. This is where we have a little "..." TODO. Turn this into an anchor tag with a span inside: className="fa fa-trash".

... lines 1 - 17
<td>
<a href="#">
<span className="fa fa-trash"></span>
</a>
</td>
... lines 23 - 34

Cool! That should get us a fancy new trash icon. Awesome.

To hook this up, we're going to go through a process that should be starting to feel familiar... hopefully boring! Here it is: when the user clicks this link in RepLogList, we ultimately need to update the state that lives in RepLogApp. That means we need to pass a handler callback from RepLogApp into RepLogs and again into RepLogList.

Adding & Calling the Delete Handler Function

In RepLogApp, create that new function: handleDeleteRepLog, which is a great name, because this component doesn't know and doesn't care that a link will be used to delete rep logs. Nope, it's all about the data. Give this an id argument so we know which rep log to delete. Be lazy and log a todo.

... lines 1 - 50
handleDeleteRepLog(id) {
console.log('todo');
}
... lines 54 - 71

Next, because we have a new handler method, make sure to bind it to this.

... lines 1 - 6
constructor(props) {
... lines 8 - 22
this.handleDeleteRepLog = this.handleDeleteRepLog.bind(this);
}
... lines 25 - 71

And finally, pass this as a new prop: onDeleteRepLog={this.handleDeleteRepLog}.

... lines 1 - 54
render() {
return (
<RepLogs
... lines 58 - 62
onDeleteRepLog={this.handleDeleteRepLog}
/>
)
}
... lines 67 - 71

Our work here is done. Now, move to RepLogs. First, at the bottom, add this to propTypes: onDeleteRepLog is PropTypes.func.isRequired.

... lines 1 - 82
highlightedRowId: PropTypes.any,
... lines 84 - 90

Above in the function, destructure onDeleteRepLog, find RepLogList, and pass this again as a prop: onDeleteRepLog={onDeleteRepLog}.

... lines 1 - 18
const {
... lines 20 - 26
} = props;
... lines 29 - 34
<div className="col-md-7">
... lines 36 - 55
highlightedRowId={highlightedRowId}
... lines 57 - 58
/>
... line 60
<tr>
... lines 62 - 79
... lines 81 - 90

Finally, move to RepLogList. Start the same: add the new prop to propTypes and destructure the variable.

... lines 1 - 3
export default function RepLogList(props) {
const { highlightedRowId, onRowClick, onDeleteRepLog, repLogs } = props;
... lines 6 - 32
}
... line 34
RepLogList.propTypes = {
... lines 36 - 37
onDeleteRepLog: PropTypes.func.isRequired,
... line 39
};

Ultimately, we need to execute this callback onClick() of the link. We have a choice here: create an inline arrow function, or add a function above render. If the logic is simple, both are fine. Add a new handleDeleteClick function with two arguments: the event and repLogId. Start with event.preventDefault() so the browser doesn't try to follow the link. Then, yep, just onDeleteRepLog(repLogId).

... lines 1 - 6
const handleDeleteClick = function(event, repLogId) {
event.preventDefault();
onDeleteRepLog(repLogId);
};
... lines 12 - 41

Scroll down to hook this up: onClick={}. Hmm, we can't call handleDeleteClick directly... because we also need to pass it the id. No worries: use an arrow function with (event) => handleDeleteClick() passing it event and - because we're inside the loop, repLog.id.

... lines 1 - 12
return (
... line 14
{repLogs.map((repLog) => (
... lines 16 - 24
<a href="#" onClick={(event) => handleDeleteClick(event, repLog.id) }>
... lines 26 - 29
))}
... line 31
);
... lines 33 - 41

Let's try it! Refresh! It looks good... and click delete. Nothing happens, but check the console. Got it! There is our todo.

Updating State: Delete from an Array without Mutating

Now for the fun part! Go back to RepLogApp. Inside the handler, we need to remove one of the repLog objects from the repLogs state. But... we do not want to modify the state. So, the question is: how can we remove an item from an array without changing that array?

Here's one great way: call this.setState() and pass it the key we want to set: repLogs. Assign this to this.state.repLogs.filter(), passing this a callback with a repLog argument. For the body, because I didn't add curly braces, we are returning repLog.id !== id.

... lines 1 - 50
handleDeleteRepLog(id) {
// remove the rep log without mutating state
// filter returns a new array
this.setState({
repLogs: this.state.repLogs.filter(repLog => repLog.id !== id)}
);
}
... lines 58 - 75

The filter function loops over each repLog, calls our function, and if it returns true, that repLog is added to the new array. This will give us a new, identical array... except without the one item.

This will work... but! You might also notice another, familiar problem. Because the new state depends on the existing state, we should pass setState() a callback to avoid a possible race condition with state being set at almost the same moment.

Call, this.setState() again, but with a callback that receives a prevState argument. Copy the object from below, delete all of that code, and return this from our callback.

... lines 1 - 53
this.setState((prevState) => {
return {
repLogs: prevState.repLogs.filter(repLog => repLog.id !== id)
};
});
... lines 59 - 77

That's it! Let's try it! Refresh and... click that trash! It's gone! We got it! And because React is awesome, there is no doubt that if I add a new item and try to delete it... yep - it works too. Because everything is based on state, there are no surprises.

Ok - it's finally time to start using AJAX to communicate with the server.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^1.6", // 1.9.1
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.3
        "doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
        "doctrine/orm": "^2.5", // v2.7.2
        "friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
        "friendsofsymfony/user-bundle": "dev-master#4125505ba6eba82ddf944378a3d636081c06da0c", // dev-master
        "sensio/framework-extra-bundle": "^5.1", // v5.2.0
        "symfony/asset": "^4.0", // v4.1.4
        "symfony/console": "^4.0", // v4.1.4
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.1.4
        "symfony/framework-bundle": "^4.0", // v4.1.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/monolog-bundle": "^3.1", // v3.3.0
        "symfony/polyfill-apcu": "^1.0", // v1.9.0
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/swiftmailer-bundle": "^3.1", // v3.2.3
        "symfony/twig-bundle": "^4.0", // v4.1.4
        "symfony/validator": "^4.0", // v4.1.4
        "symfony/yaml": "^4.0", // v4.1.4
        "twig/twig": "2.10.*" // v2.10.0
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.6
        "symfony/dotenv": "^4.0", // v4.1.4
        "symfony/maker-bundle": "^1.5", // v1.5.0
        "symfony/phpunit-bridge": "^4.0", // v4.1.4
        "symfony/web-server-bundle": "^4.0" // v4.1.4
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "dependencies": {
        "@babel/plugin-proposal-object-rest-spread": "^7.12.1" // 7.12.1
    },
    "devDependencies": {
        "@babel/preset-react": "^7.0.0", // 7.12.5
        "@symfony/webpack-encore": "^0.26.0", // 0.26.0
        "babel-plugin-transform-object-rest-spread": "^6.26.0", // 6.26.0
        "babel-plugin-transform-react-remove-prop-types": "^0.4.13", // 0.4.13
        "bootstrap": "3", // 3.3.7
        "copy-webpack-plugin": "^4.4.1", // 4.5.1
        "core-js": "2", // 1.2.7
        "eslint": "^4.19.1", // 4.19.1
        "eslint-plugin-react": "^7.8.2", // 7.8.2
        "font-awesome": "4", // 4.7.0
        "jquery": "^3.3.1", // 3.3.1
        "promise-polyfill": "^8.0.0", // 8.0.0
        "prop-types": "^15.6.1", // 15.6.1
        "react": "^16.3.2", // 16.4.0
        "react-dom": "^16.3.2", // 16.4.0
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^7.0.0", // 7.3.1
        "sweetalert2": "^7.11.0", // 7.22.0
        "uuid": "^3.2.1", // 3.4.0
        "webpack-notifier": "^1.5.1", // 1.6.0
        "whatwg-fetch": "^2.0.4" // 2.0.4
    }
}
userVoice