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.


33. Controlled vs. Uncontrolled Components

Objective: Drive home the importance of creating controlled form inputs by fixing the value of the GenerateIngredients input component to the current state.

In this step, we are just adding value={ingredient[name]} to the <Input> component. This changes how the component works- previously any changes that were made in the input were saved into the state in the submitForm hook. When we submitted the form we'd take the data from that object and submit it as a mutation. The problem was that any changes that were made to the state did not make it back into the <Input> component because the value was not fixed to the state of the submitForm. By specifying that the value of the <Input> is equal to the state ingredient[name], we ensure that state is only being stored in a single location (submitForm) instead of what we had before which was that it was stored in two locations (submitForm and internal state of <Input>) that were synced together through the use of the handleInputChange onChange handler.

components/GenerateIngredients.tsx

import { Row, Col, Button, Table, Input, Dropdown } from 'antd';
import { MenuList } from './MenuList';
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 = 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}
/>
);
},
}));
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}
</>
);
};