Comece criando a pasta “contexts” e, dentro dela, o arquivo “StockContext.jsx”:
import { createContext, useState } from "react";
import PropTypes from "prop-types"
export const StockContext = createContext({})
StockContextProvider.propTypes = {
children: PropTypes.node
}
export function StockContextProvider({ children }) {
const [items, setItems] = useState(() => {
const storedItems = localStorage.getItem('obc-react-stock')
if (!storedItems) return []
const items = JSON.parse(storedItems)
items.forEach((item) => {
item.createdAt = new Date(item.createdAt)
item.updatedAt = new Date(item.updatedAt)
})
return items
})
const addItem = (item) => {
setItems(current => {
const updatedItems = [item, ...current]
localStorage.setItem('obc-react-stock', JSON.stringify(updatedItems))
return updatedItems
})
}
const getItem = (itemId) => {
return items.find(i => i.id === +itemId)
}
const updateItem = (itemId, newAttributes) => {
setItems(current => {
const itemIndex = current.findIndex(i => i.id === itemId)
const updatedItems = [...current]
Object.assign(updatedItems[itemIndex], newAttributes, { updatedAt: new Date() })
localStorage.setItem('obc-react-stock', JSON.stringify(updatedItems))
return updatedItems
})
}
const deleteItem = (itemId) => {
setItems(current => {
const updatedItems = current.filter(item => item.id !== itemId)
localStorage.setItem('obc-react-stock', JSON.stringify(updatedItems))
return updatedItems
})
}
const stock = {
items,
addItem,
getItem,
updateItem,
deleteItem
}
return (
<StockContext.Provider value={stock}>
{children}
</StockContext.Provider>
)
}
Crie também a pasta “hooks” e o arquivo “useStock.js”:
import { useContext } from "react";
import { StockContext } from "../contexts/StockContext";
export default function useStock() {
return useContext(StockContext)
}
Crie agora a pasta “components” e, dentro dela, crie o arquivo “ItemsTable.jsx”:
import { Link } from "react-router-dom";
import useStock from "../hooks/useStock";
export default function ItemsTable() {
const { items } = useStock();
return (
<table>
<thead>
<tr>
<th>ID</th>
<th>Nome</th>
<th>Em Estoque</th>
<th>Categoria</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{items.map((item) => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.quantity} unid.</td>
<td>{item.category}</td>
<td>
<Link to={`/items/${item.id}`} className="button is-primary is-small">
Ver
</Link>
<Link to={`/items/${item.id}/update`} className="button is-small">
Atualizar
</Link>
</td>
</tr>
))}
</tbody>
</table>
)
}
Antes de prosseguir, crie uma pasta “entities” e uma classe “StockItem.js” para auxiliar na criação de itens:
export const CATEGORIES = [
"Jogos",
"Livros",
"Brinquedos",
"Acessórios"
]
export default class StockItem {
constructor({ name, description, quantity, price, category }) {
this.id = Math.floor(Math.random() * 10000000)
this.name = name
this.description = description
this.quantity = +quantity
this.price = +price
this.category = category
this.createdAt = new Date()
this.updatedAt = new Date()
this.#validate()
}
#validate() {
const validName = typeof this.name === "string"
const validDescription = typeof this.description === "string"
const validQuantity = typeof this.quantity === "number" && Number.isInteger(this.quantity)
const validPrice = typeof this.price === "number"
const validCategory = CATEGORIES.includes(this.category)
if (!(
validName &&
validDescription &&
validQuantity &&
validPrice &&
validCategory
)) {
throw new Error("Invalid item!")
}
}
}
Agora crie o componente “ItemForm.jsx” na pasta “components”. Ele será usado tanto para criar um novo item quanto para atualizar um item existente:
import PropTypes from "prop-types"
import StockItem, { CATEGORIES } from "../entities/StockItem"
import { useRef, useState } from "react"
import useStock from "../hooks/useStock"
ItemForm.propTypes = {
itemToUpdate: PropTypes.object
}
export default function ItemForm({ itemToUpdate }) {
const defaultItem = {
name: "",
description: "",
quantity: 0,
price: 0,
category: ""
}
const [item, setItem] = useState(itemToUpdate ? itemToUpdate : defaultItem)
const { addItem, updateItem } = useStock()
const inputRef = useRef(null)
const handleChange = (ev) => {
setItem((current) => ({ ...current, [ev.target.name]: ev.target.value }))
}
const handleSubmit = (ev) => {
ev.preventDefault()
try {
if (itemToUpdate) {
updateItem(itemToUpdate.id, item)
alert("Item atualizado com sucesso!")
} else {
const validItem = new StockItem(item)
addItem(validItem)
setItem(defaultItem)
alert("Item cadastrado com sucesso!")
}
} catch (err) {
console.log(err.message)
alert("Ocorreu um erro.")
} finally {
inputRef.current.focus()
}
}
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div>
<label htmlFor="name">Nome</label>
<input
type="text"
name="name"
id="name"
required
ref={inputRef}
value={item.name}
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="quantity">Quantidade</label>
<input
type="number"
name="quantity"
id="quantity"
required
min={0}
step={1}
value={item.quantity}
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="price">Preço</label>
<input
type="number"
name="price"
id="price"
required
min={0.00}
step={0.01}
value={item.price}
onChange={handleChange}
/>
</div>
<div>
<label htmlFor="category">Categoria</label>
<select
name="category"
id="category"
required
value={item.category}
onChange={handleChange}
>
<option disabled value="">Selecione uma categoria...</option>
{CATEGORIES.map((category) => (
<option
key={category}
value={category}
defaultChecked={item.category === category}
>
{category}
</option>
))}
</select>
</div>
</div>
<div className="form-control">
<label htmlFor="description">Descrição</label>
<textarea
name="description"
id="description"
required
rows={6}
value={item.description}
onChange={handleChange}
></textarea>
</div>
<button className="button is-primary is-large">
Salvar
</button>
</form>
)
}
Por fim, adicione os componentes de tabela e formulário nas suas respectivas telas:
src/pages/ListItems.jsx
import ItemsTable from "../../components/ItemsTable";
export default function ListItems() {
return <ItemsTable />
}
src/pages/CreateItem.jsx
import ItemForm from "../../components/ItemForm";
export default function CreateItem() {
return <ItemForm />
}