Frontend Serverless with React and GraphQL

Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.


34. Add a Delete Ingredient Button

Objective: Create a delete button for the GenerateIngredients component that will allow users to remove ingredients from the list.

We need to create a delete button for each ingredient to ensure that users can remove unneeded ingredients from the list. We can add an additional column by concatinating a delete column onto the list of columns that we already have.

components/GenerateIngredients.tsx

import { Row, Col, Button, Table, Input, Dropdown } from 'antd';
import { MenuList } from './MenuList';
import * as _ from 'lodash';
type IngredientsProps = {
names?: string[];
values?: [{ amount: string; unit: string; type: string }];
handleAddIngredient: (event: any) => void;
handleDeleteIngredient: (event: any) => void;
handleInputChange: (event: any) => void;
handleDropdownChange: (event: any) => void;
};
const units = ['-', 'ounce', 'lb', 'cup', 'tb', 'tsp', 'g', 'kg'];
export const GenerateIngredients = ({
names,
values,
handleAddIngredient,
handleDeleteIngredient,
handleInputChange,
handleDropdownChange,
}: IngredientsProps) => {
const columns = _.concat(
names.map((name) => ({
title: `${name}`,
key: `${name}`,
render: (ingredient, _record, index: number) => {
return name === 'unit' ? (
<Dropdown
overlay={
<MenuList
iterableList={units}
name={`ingredients[${index}].${name}`}
handleDropdownChange={handleDropdownChange}
/>
}
placement="bottomLeft"
>
<Button>{ingredient[name]}</Button>
</Dropdown>
) : (
<Input
value={ingredient[name]}
placeholder={`${name}`}
name={`ingredients[${index}].${name}`}
onChange={handleInputChange}
/>
);
},
})),
[
{
title: 'delete',
key: 'delete',
render: (_ingredient, _record, index: number) => (
<Button
onClick={handleDeleteIngredient}
type="danger"
shape="circle"
size="small"
name={`${index}`}
>
-
</Button>
),
},
],
);
return (
<>
<Row>
<Col span={12} offset={6}>
<p>
<Button
onClick={handleAddIngredient}
type="primary"
shape="circle"
size="small"
>
+
</Button>
ingredients:
</p>
</Col>
</Row>
{values.length > 0 ? (
<Row>
<Col span={12} offset={6}>
<Table
dataSource={values}
columns={columns}
pagination={{ pageSize: 25 }}
/>
</Col>
</Row>
) : null}
</>
);
};

The delete button will call a delete ingredient handler, so we need to now create this in the submitForm hook and pass it into the <GenerateIngredients> component

utils/submitForm.ts

import { useState } from 'react';
import * as _ from 'lodash';
export const submitForm = (initialValues, callback) => {
const [inputs, setInputs] = useState(initialValues);
const handleInputChange = (event) => {
event.persist();
setInputs((inputs) => {
const newInputs = _.cloneDeep(inputs);
_.set(newInputs, event.target.name, event.target.value);
return newInputs;
});
};
const handleDropdownChange = (event) => {
setInputs((inputs) => {
const newInputs = _.cloneDeep(inputs);
_.set(newInputs, event.item.props.title, event.key);
return newInputs;
});
};
const handleAddIngredient = (event) => {
event.persist();
setInputs((inputs) => {
const sortedIngredients = _.sortBy(inputs.ingredients, ['key']);
const key =
sortedIngredients.length > 0
? sortedIngredients[sortedIngredients.length - 1].key + 1
: 0;
return {
...inputs,
ingredients: _.concat(inputs.ingredients, [
{ key, amount: '', unit: '-', type: '' },
]),
};
});
};
const handleDeleteIngredient = (event) => {
console.log('deleted');
event.persist();
const position = parseInt(event.target.name);
setInputs((inputs) => ({
...inputs,
ingredients: _.filter(
inputs.ingredients,
(_i, index) => index !== position,
),
}));
};
const handleSubmit = () => {
callback();
setInputs(() => ({ ...initialValues }));
};
return {
inputs,
handleSubmit,
handleInputChange,
handleAddIngredient,
handleDeleteIngredient,
handleDropdownChange,
};
};