21 - Formularios: control select dependiente de otro control select con peticiones a un servidor

Vimos en el concepto anterior una situación común de varios controles select que dependen entre si.

Problema

Vamos a crear un control select que muestre distintos rubros (por ejemplo: Microprocesadores, Placas de video, Gabinetes etc), luego cuando el operador selecciona un rubro, se debe actualizar otro control select que muestra todos los artículos que pertenecen a dicho rubro, por ejemplo si seleccionar Microprocesadores el segundo select puede mostrar Intel Core I5, Intel Core I7 etc., pero ahora los datos se encontrarán en un servidor web y haremos peticiones con la función fetch. El servidor nos responderá con formato JSON.

  1. Como primer paso creamos una aplicación con create-react-app:

    npx create-react-app proyecto021
    
  2. Veamos el código de nuestra aplicación donde se encuentra la funcionalidad del formulario.

    App.js

    import './App.css';
    import { useState, useEffect } from 'react';
    
    
    function App() {
    
      const [rubros, setRubros] = useState([])
      const [rubroSeleccionado, setRubroSeleccionado] = useState({})
    
      useEffect(() => {
        fetch('https://www.scratchya.com.ar/reactya/proyecto021/recuperarrubros.php')
          .then((response) => {
            return response.json()
          })
          .then((rub) => {
            setRubros(rub)
            setRubroSeleccionado(rub[0])
          })
      }, [])
    
      const [articulosRubro, setarticulosRubro] = useState([])
      const [articuloSeleccionado, setArticuloSeleccionado] = useState([])
    
      useEffect(() => {
        if (rubroSeleccionado.codigo)
          fetch('https://www.scratchya.com.ar/reactya/proyecto021/recuperararticulos.php?rubro=' + rubroSeleccionado.codigo)
            .then((response) => {
              return response.json()
            })
            .then((art) => {
              setarticulosRubro(art)
              setArticuloSeleccionado(art[0])
            })
      }, [rubroSeleccionado])
    
    
    
      function cambiarRubro(e) {
        const rubroSelect = rubros.find(r => Number.parseInt(r.codigo) === Number.parseInt(e.target.value))
        setRubroSeleccionado(rubroSelect)
      }
    
      function cambiarArticulo(e) {
        setArticuloSeleccionado(articulosRubro.find(articulo => Number.parseInt(articulo.codigo) === Number.parseInt(e.target.value)))
      }
    
      return (
        <div className="formulario">
          <div>
            <select value={rubroSeleccionado.codigo} onChange={cambiarRubro}>
              {rubros.map(rubro => (
                <option key={rubro.codigo} value={rubro.codigo}>{rubro.nombre}</option>
              ))}
            </select>
          </div>
          <div>
            <select value={articuloSeleccionado.codigo} onChange={cambiarArticulo}>
              {articulosRubro.map(articulo => (
                <option key={articulo.codigo} value={articulo.codigo}>{articulo.nombre}</option>
              ))}
            </select>
          </div>
          <div>
            <ul>
              <li>Rubro:<strong>{rubroSeleccionado.nombre}</strong></li>
              <li>Articulo:<strong>{articuloSeleccionado.nombre}</strong></li>
              <li>Precio:<strong>{articuloSeleccionado.precio}</strong></li>
            </ul>
          </div>
        </div>
      );
    }
    
    export default App;
    

    Importamos las funciones useState y useEffect:

    import { useState, useEffect } from 'react';
    

    Mediante un hook de efecto recuperamos todos los rubres del servidor y procedemos a actualizar la variable rubros:

      const [rubros, setRubros] = useState([])
      const [rubroSeleccionado, setRubroSeleccionado] = useState({})
    
      useEffect(() => {
        fetch('https://www.scratchya.com.ar/reactya/proyecto021/recuperarrubros.php')
          .then((response) => {
            return response.json()
          })
          .then((rub) => {
            setRubros(rub)
            setRubroSeleccionado(rub[0])
          })
      }, [])
    

    Cuando los datos llegan del servidor procedemos a mostrar todos los rubros y marcar como seleccionado el primero:

            <select value={rubroSeleccionado.codigo} onChange={cambiarRubro}>
              {rubros.map(rubro => (
                <option key={rubro.codigo} value={rubro.codigo}>{rubro.nombre}</option>
              ))}
            </select>
    

    De forma similar recuperamos todos los artículos que coinciden con el rubro seleccionado actual:

      const [articulosRubro, setarticulosRubro] = useState([])
      const [articuloSeleccionado, setArticuloSeleccionado] = useState([])
    
      useEffect(() => {
        if (rubroSeleccionado.codigo)
          fetch('https://www.scratchya.com.ar/reactya/proyecto021/recuperararticulos.php?rubro=' + rubroSeleccionado.codigo)
            .then((response) => {
              return response.json()
            })
            .then((art) => {
              setarticulosRubro(art)
              setArticuloSeleccionado(art[0])
            })
      }, [rubroSeleccionado])
    

    Como vemos el servidor nos retorna solo los artículos del rubro seleccionado actualmente. Luego al modificar la variable 'articulosRubro' se actualiza el control select en forma automática:

            <select value={articuloSeleccionado.codigo} onChange={cambiarArticulo}>
              {articulosRubro.map(articulo => (
                <option key={articulo.codigo} value={articulo.codigo}>{articulo.nombre}</option>
              ))}
            </select>
    

    el evento de cambio de rubro dispara la función 'cambiarRubro' donde recuperamos el nuevo rubro seleccionado y actualizamos la variable 'rubroSeleccionado', el cual dispara nuevamente el hook de efecto que recupera los artículos del nuevo rubro seleccionado:

      function cambiarRubro(e) {
        const rubroSelect = rubros.find(r => Number.parseInt(r.codigo) === Number.parseInt(e.target.value))
        setRubroSeleccionado(rubroSelect)
      }
    
      function cambiarArticulo(e) {
        setArticuloSeleccionado(articulosRubro.find(articulo => Number.parseInt(articulo.codigo) === Number.parseInt(e.target.value)))
      }
    

    En el archivo de la hoja de estilo.

    App.css

    select {
      padding: 0.5em;
      margin: 0.5em;
    }
    
    .formulario {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    

    Podemos probar ahora la aplicación: aquí