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.

  1. 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
    
  2. 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>
      )
    }
    
  3. 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
    
  4. 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>
      )
    }
    
  5. 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>
      )
    }
    
  6. 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>
      )
    }
    
  7. 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
    
  8. 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 }
    }
    
  9. 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