20 - Formularios: control select dependiente de otro control select

Una situación común en un formulario web es cuando un control select depende del elemento seleccionado de otro control select.

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.

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

    npx create-react-app proyecto020
    
  2. Crearemos en un archivo separado los dos arreglos de objetos donde se almacenan los rubros y los artículos: datos.js (en la misma carpeta donde se localiza App.js):

    datos.js

    const rubros = [{
      codigo: 1,
      nombre: 'Microprocesadores'
    },
    {
      codigo: 2,
      nombre: 'Tarjetas de video'
    },
    {
      codigo: 3,
      nombre: 'Gabinetes'
    }
    ]
    
    const articulos = [{
      codigo: 1,
      codigorubro: 1,
      nombre: 'Microprocesador Intel Core i5',
      precio: 1000
    },
    {
      codigo: 2,
      codigorubro: 1,
      nombre: 'Microprocesador Intel Core i7',
      precio: 2000
    },
    {
      codigo: 3,
      codigorubro: 1,
      nombre: 'Microprocesador Intel Core i9',
      precio: 3000
    },
    {
      codigo: 4,
      codigorubro: 2,
      nombre: 'Tarjeta de video MSI GeForce GTX 1080',
      precio: 790
    },
    {
      codigo: 5,
      codigorubro: 2,
      nombre: 'Tarjeta de video MSI GeForce GTX 1060',
      precio: 1200
    },
    {
      codigo: 6,
      codigorubro: 2,
      nombre: 'Tarjeta de video MSI GeForce GTX 1050',
      precio: 1800
    },
    {
      codigo: 7,
      codigorubro: 3,
      nombre: 'Gabinete MSI',
      precio: 840
    },
    {
      codigo: 8,
      codigorubro: 3,
      nombre: 'Gabinete Cooler Master 200 Black',
      precio: 1120
    }
    ]
    
    
    export {articulos}
    export {rubros}
    

    rubros es un Array de 3 elementos de tipo objeto donde almacenamos el codigo del rubro y su nombre. articulos es un segundo Array donde almacenamos objetos con los atributos código, el codigo de rubro, nombre y el precio del artículo.

  3. Veamos el código principal de nuestra aplicación donde se encuentra la funcionalidad del formulario.

    App.js

    import './App.css'
    import { useState } from 'react'
    import { rubros } from './datos.js'
    import { articulos } from './datos.js'
    
    
    function App() {
    
      const [rubro, setRubro] = useState(rubros[0])
      const [articulosRubro, setarticulosRubro] = useState(articulos.filter(articulo => articulo.codigorubro === rubro.codigo))
      const [articulo, setArticulo] = useState(articulosRubro[0])
    
      function cambiarRubro(e) {
        setRubro(rubros.find(rubro => rubro.codigo === Number.parseInt(e.target.value)))
        const articulosrubro = articulos.filter(articulo => articulo.codigorubro === Number.parseInt(e.target.value))
        setarticulosRubro(articulosrubro)
        setArticulo(articulosrubro[0])
      }
    
      function cambiarArticulo(e) {
        setArticulo(articulosRubro.find(articulo => articulo.codigo === Number.parseInt(e.target.value)))
      }
    
      return (
        <div className="formulario">
          <div>
            <select value={rubro.codigo} onChange={cambiarRubro}>
              {rubros.map(rubro => (
                <option key={rubro.codigo} value={rubro.codigo}>{rubro.nombre}</option>
              ))}
            </select>
          </div>
          <div>
            <select value={articulo.codigo} onChange={cambiarArticulo}>
              {articulosRubro.map(articulo => (
                <option key={articulo.codigo} value={articulo.codigo}>{articulo.nombre}</option>
              ))}
            </select>
          </div>
          <div>
            <ul>
              <li>Rubro:<strong>{rubro.nombre}</strong></li>
              <li>Articulo:<strong>{articulo.nombre}</strong></li>
              <li>Precio:<strong>{articulo.precio}</strong></li>
            </ul>
          </div>
        </div>
      );
    }
    
    export default App;
    

    Importamos los arreglos definidos en el archivo 'datos.js':

    import { rubros } from './datos.js'
    import { articulos } from './datos.js'
    

    El primer control select mediante el método map procedemos a inicializar el atributo value de cada 'option' y muestra en la etiqueta 'rubro.nombre'. Por otro lado inicializamos la propiedad value de la etiqueta select indicando cual debe mostrarse seleccionado y el evento 'onChange':

    Dbemos inicializar

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

    Utilizamos un hook de estado para almacenar un objeto que representa el rubro seleccionado:

      const [rubro, setRubro] = useState(rubros[0]);
    

    Un segundo hook nos permite almacenar en otro arreglo todos los artículos que pertenecen al rubro seleccionado (por eso filtramos los artículos que pertenecen al rubro seleccionado):

      const [articulosRubro, setarticulosRubro] = useState(articulos.filter(articulo => articulo.codigorubro === rubro.codigo));
    

    Este arreglo almacenado en 'articulosRubro' nos permite actualizar el segundo control select en pantalla:

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

    Un tercer hook nos permite indicar que artículo debe aparecer seleccionado en el segundo control select:

      const [articulo, setArticulo] = useState(articulosRubro[0]);
    

    Cuando el operador cambia el rubro seleccionado en el primer control select, se dispara la función 'cambiarRubro' donde actualizamos el rubro seleccionado con el código de rubro que recuperamos del parámetro 'e' y actualizamos el arreglo con los artículos que pertenecen al nuevo rubro. También dejamos seleccionado el primer artículo en el segundo 'select':

     
      function cambiarRubro(e) {
        setRubro(rubros.find(rubro => rubro.codigo === Number.parseInt(e.target.value)))
        const articulosrubro = articulos.filter(articulo => articulo.codigorubro === Number.parseInt(e.target.value))
        setarticulosRubro(articulosrubro)
        setArticulo(articulosrubro[0])
      }
    

    Cuando cambiamos de artículo en el segundo select procedemos a actualizar el artículo a mostrar seleccionado:

      function cambiarArticulo(e) {
        setArticulo(articulosRubro.find(articulo => 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í