E aí, programador! o/
Nessa aula vamos refatorar nosso projeto da biblioteca de jogos. Iremos trabalhar conceitos como componentização e separação de responsabilidades para melhorar a qualidade do código da aplicação.
Comece criando a pasta “components” e nela 3 arquivos: “NewGameForm.jsx”, “TextInput.jsx” e “Game.jsx”. Além disso, instale a biblioteca “prop-types”:
npm i prop-types
No componente “Game.jsx” adicione o código de renderização de um jogo:
import PropTypes from "prop-types"
Game.propTypes = {
title: PropTypes.string,
cover: PropTypes.string,
onRemove: PropTypes.func
}
export default function Game({ title, cover, onRemove }) {
return (
<div>
<img src={cover} alt="Capa do jogo" />
<div>
<h2>{title}</h2>
<button onClick={onRemove}>
Remover
</button>
</div>
</div>
)
}
Substitua o código dentro do .map() em “App.jsx” pelo novo componente Game:
// ...
<div className="games">
{games.map((game) => (
<Game
key={game.id}
title={game.title}
cover={game.cover}
onRemove={() => removeGame(game.id)}
/>
))}
</div>
</div>
)
}
export default App
Mova toda a parte relacionada ao formulário para o componente “Form.jsx”:
import { useState } from "react"
import PropTypes from "prop-types"
NewGameForm.propTypes = {
addGame: PropTypes.func
}
export default function NewGameForm({ addGame }) {
const [title, setTitle] = useState("")
const [cover, setCover] = useState("")
const handleSubmit = (ev) => {
ev.preventDefault()
addGame({ title, cover })
setTitle("")
setCover("")
}
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="title">Título:</label>
<input type="text" id="title" value={title} onChange={(ev) => setTitle(ev.target.value)} />
</div>
<div>
<label htmlFor="cover">Capa:</label>
<input type="text" id="cover" value={cover} onChange={(ev) => setCover(ev.target.value)} />
</div>
<button>Adicionar</button>
</form>
)
}
No componente “TextInput.jsx” crie um conjunto genérico de label e input:
import PropTypes from "prop-types"
TextInput.propTypes = {
id: PropTypes.string,
label: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func
}
export default function TextInput({ id, label, value, onChange }) {
return (
<div>
<label htmlFor={id}>{label}</label>
<input type="text" id={id} value={value} onChange={onChange} />
</div>
)
}
Substitua o conteúdo do form em “NewGameForm.jsx” pelo componente criado:
import { useState } from "react"
import PropTypes from "prop-types"
import TextInput from "./TextInput"
// ...
<form onSubmit={handleSubmit}>
<TextInput id="title" label="Título" value={title} onChange={(ev) => setTitle(ev.target.value)} />
<TextInput id="cover" label="Capa" value={cover} onChange={(ev) => setCover(ev.target.value)} />
<button>Adicionar</button>
</form>
)
}
E substitua o formulário em “App.jsx” pelo novo formulário criado:
// ...
return (
<div className="app">
<h1>Biblioteca de Jogos</h1>
<NewGameForm addGame={addGame} />
<div className="games">
{games.map((game) => (
<Game
key={game.id}
title={game.title}
cover={game.cover}
onRemove={() => removeGame(game.id)}
/>
))}
</div>
</div>
)
}
export default App
Por fim, crie uma pasta “hooks” e, dentro dela, o arquivo “useGameCollection.js” para armazenar toda a lógica da coleção de jogos e o gerenciamento do estado:
import { useState } from "react"
export default function useGameCollection() {
const [games, setGames] = useState(() => {
const storedGames = localStorage.getItem("obc-game-lib")
if (!storedGames) return []
return JSON.parse(storedGames)
})
const addGame = ({ title, cover }) => {
const id = Math.floor(Math.random() * 1000000)
const game = { id, title, cover }
setGames(state => {
const newState = [...state, game]
localStorage.setItem("obc-game-lib", JSON.stringify(newState))
return newState
})
}
const removeGame = (id) => {
setGames(state => {
const newState = state.filter(game => game.id !== id)
localStorage.setItem("obc-game-lib", JSON.stringify(newState))
return newState
})
}
return { games, addGame, removeGame }
}
E atualize o arquivo “App.jsx” para usar o hook:
import NewGameForm from "./components/NewGameForm"
import Game from "./components/Game"
import useGameCollection from "./hooks/useGameCollection"
function App() {
const { games, addGame, removeGame } = useGameCollection()
return (
<div className="app">
<h1>Biblioteca de Jogos</h1>
<NewGameForm addGame={addGame} />
<div className="games">
{games.map((game) => (
<Game
key={game.id}
title={game.title}
cover={game.cover}
onRemove={() => removeGame(game.id)}
/>
))}
</div>
</div>
)
}
export default App