No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Adquiere por un año todos los cursos, escuelas y certificados por un precio especial.

Antes: $249

Currency
$219/año

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Comprar ahora

Termina en:

0D
2H
35M
45S
Curso Profesional de Next.js

Curso Profesional de Next.js

Oscar Barajas Tavares

Oscar Barajas Tavares

Creación y cargado de datos para actualizar un producto

25/31
Recursos

Aportes 14

Preguntas 3

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

o inicia sesión.

El problema en este caso sería que podríamos colocar una ID que no exista y eso rompería la página. Para evitarlo podemos crear una constante notFound

const [notFound, setNotFound] = useState(false);

Luego crear un catch que, en el caso de que no encuentre la ID, cambie notFound a true

async function getProduct() {
			try {
				const response = await axios.get(endPoints.products.getProduct(id));

				if (response) {
					setProduct(response.data);
				}
			} catch (error) {
				console.log(error);
				setNotFound(true);
			}
		}

Y por último condicionar nuestro return al caso de que encuentre o no el ID

return notFound ? <div> Product Not Found </div> : <FormProduct product={product} />;

Para esas personas que se estan haciendo un lio para cambiar el value del select de la categoría es tan simple como poner esto:

  // Se usa el hook del cambio de estado sobre los productos
  useEffect(() => {
    // Se coge la referencia al nodo de HTML del select
    const categoryTag = document.querySelector('#category');
    // Se cambiar el valor del nodo por el valor del id; eso hace que el valor del <option> cambie tambien
    categoryTag.value = product?.category?.id;
  }, [product]);

Recordemos que React y Next siguen siendo JavaScript, no hay que inventar cosas raras para manipular el DOM

Clase # 25: Creación y cargado de datos para actualizar un producto 25/31 🎨


 

Continuando con el Proyecto: 🔧

 
Vamos a crear la estructura para actualizar uno de los productos por medio del navegador. Lo que se quiere es que el usuario al dar click en la opción “Edit” de cada producto, haga el enlace con la referencia de editar el producto y aparezca el cuadro con los campos para editar los atributos del producto.
 
Vamos a VSC y en la carpeta de la ruta principal de las páginas: src/pages creamos una nueva carpeta llamada edit que contendrá la ruta dinámica que por medio de un id identificará a cuál producto se hará el cambio. Dentro de esa carpeta edit, se crea el archivo con corchetes [id].js Éste id dinámico lo vamos a obtener por medio de la url, éste id será capturado por useRouter() de next, que se hará una solicitud a la API para que pueda traer los datos del producto y de ésta forma hacer render dentro del formulario para poder cambiar cualquier elemento y luego guardarlo.
 
La lógica de [id].js queda:

import FormProduct from '@components/FormProduct';
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import axios from 'axios';
import endPoints from '@services/api';

//La función edit retorna el formulario para pasarle un nuevo valor del producto
export default function Edit() {
	const [product, setProduct] = useState({}); //Donde se almacena la información
	const router = useRouter();

	useEffect(() => {
		const { id } = router.query;
		if (!router.isReady) return; //Si no tenemos el id, retorna y no hace el llamado
		//Se hace el llamado con getProduct
		async function getProduct() {
			const response = await axios.get(endPoints.products.getProduct(id));
			setProduct(response.data);
		}
		getProduct();
	}, [router?.isReady]); //Para saber que ya está disponible

	return <FormProduct product={product} />;
}

 
Luego en el archivo FormProduct.js de la ruta src/components se agrega a la función Products el parámetro product:

export default function FormProduct({setOpen, setAlert, product})

 
Dentro del return, en cada elemento del producto se le asigna el valor por defecto:
 

  • Para el nombre:
<input defaultValue={product?.title}

 

  • Para el precio:
<input defaultValue={product?.price}

 

  • Para la categoría:
<select
id="category"
name="category"
defaultValue={product?.categoryId}

 

  • Para la descripción:
<textarea
defaultValue={product?.description}

 

  • Para la imagen:
<input defaultValue={product?.images}

 
Guardamos, corremos npm run dev vamos al navegador y colocamos la url: http://localhost:3000/dashboard/edit/7
 
Al final podemos cambiar el número que corresponde al id y sale el cuadro del Form para ingresar los datos a actualizar.
 

Los que quieran solucionar el problema de la categoría, cambien la sigueinte línea:

defaultValue={product?.category?.id}

Por esto:

value={product?.category?.id.toString()}

.
Convierto el número de categoría a String porque el select espera un String pero lo recibido es un Number

Mmm yo creo que falta agregar algo, el tema de category no funciona bien ya que en el llamado obtenemos un objeto más no un id, aparte de que el defaultValue en el select no funciona, se me ocurrió una manera de solucionarlo, agregamos un ref a ese select, luego cuando se cargue el componente agregamos un useEffect que escuche el cambio del producto con un switch:

const categorySelect = useRef(null)

  useEffect(() => {
    switch (product?.category?.id) {
      case 1:
        categorySelect.current.children[0].selected = true
        break
      case 2:
        categorySelect.current.children[1].selected = true
        break
      case 3:
        categorySelect.current.children[2].selected = true
        break
      case 4:
        categorySelect.current.children[3].selected = true
        break
      case 5:
        categorySelect.current.children[4].selected = true
        break
      default:
        break
    }
  }, [product])

Así, cuando cargue el component, lograremos que aparezca con la categoría correcta.

yo le añadí los casos de loading y error cuando hacemos la petición, de esta forma podemos controlar los casos en los que se presente un problema al realizar la petición o cuando esperamos por la data
src/pages/dashboard/edit/[id].js:

import React, { useEffect, useState } from 'react';
import { useRouter } from "next/router";
import axios from "axios";
import endPoints from "@services/api";
import FormProduct from "@components/FormProduct";

const Edit = () => {
    const router = useRouter();
    const [product, setProducts] = useState({});
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(false);
    
    useEffect(()=> {
        setLoading(true);
        const { id } = router.query
        
        const getProduct = async ()=> {
            const response = await axios.get(endPoints.products.getProduct(id));
            return response.data
        }

        getProduct()
            .then(res => {
                setLoading(false);
                setProducts(res)
            })
            .catch(err => {
                setLoading(false);
                setError(true);
            })
    }, [router?.isReady])

    if (loading) {
        return <h1>Loading ...</h1>
    }   

    if (error){
        return <h1>Not Found: request failed</h1>
    }

    return (
        <FormProduct product={product} />
    );
}

export default Edit;

para el formulario, cambie el valor por defecto de las categorías, accediendo a la propiedad id de category

defaultValue={product?.category?.id}

Si hacen peticiones y el servidor les avienta un error 404 es porque probablemente el ID el cual están solicitando ya no exista
,para ello podrían hacer una validación y ver si el ID primero existe y luego enviar la petición. He visto que algunos compañeros ya han pasado el como hacerlo

Para manejar el caso en el que el id que se manda por la URL no exista, modifiqué el useEffect de [id].js de la siguiente manera:
En pages creé un archivo llamado notFound.js para crear esa ruta.

React.useEffect(() => {
    //El id se obtendrá desde la URL
    const {id} = router.query;
    //Si la ruta aún no está lista, entonces el id tampoco
    if (!router.isReady) return;
    //Función para obtener la info del producto a partir del id
    const getProduct = async () => {
      const response = await axios.get(endPoints.products.getProduct(id));
      return response;
    };
    //Si la promesa se resuelve, asignamos la data a product
    getProduct()
      .then((response) => setProduct(response.data))
      //Si el id no existe, mandamos a notFound
      .catch((err) => router.push('/notFound'));
  }, [router?.isReady]);

Y para arreglar el problema con el select de las categorías agregué lo siguiente:

FormProduct.jsx

const categorySelect = React.useRef(null);
React.useEffect(() => {
    if (product) {
      categorySelect.current.children[product?.category.id - 1].selected = true;
    }
  }, [product]);

Al mostrar los datos del producto a editar, no se selecciona la categoría correspondiente, lo que hice fue usar los hooks ‘useEffect’ y ‘useState’ para obtener la categoría del producto:

const [categoryDefaultValue, setCategoryDefaultValue] = useState(1);

useEffect(() => {
	if (product) {
		setCategoryDefaultValue(product?.category?.id);
	}
}, [product]);

Y en el select de la categoría agregué en el ‘value’ (en el ‘defaultValue’ no funciona):

<select
	value={categoryDefaultValue}
	onChange={(e) => setCategoryDefaultValue(parseInt(e.target.value))}

El ‘onChange’ es necesario, si no se agrega marca un error en consola de que debes agregarlo, ya que has definido directamente el ‘value’ del select.

Con respecto al category yo lo resolvi añadiendo lo siguiente antes del return (…):

if (formRef?.current && product) {
//formData captura cada elemento del input
const formData = new FormData(formRef.current);
formData.set(‘category’, product?.category?.id)
}

Así lo logré con Next 13:

'use client'
import { useEffect, useState } from 'react'
import axios from 'axios'
import { endpoints } from '@/libs/endpoints.api' 
import { CreateProduct } from '@/types/Product'
import Form from '@/components/Form'

export default function Edit({ params }: { params: { id: string } }) {
  const [product, setProduct] = useState<CreateProduct>()
  const id =  params.id

  useEffect(() => {
    if (!id) return

    const getProduct = async () => {
      const response = await axios.get(endpoints.products.getProduct(id));
      console.log(id, response.data);
      setProduct(response.data)
    }
    getProduct();
  }, [id])

  return (
    <Form product={product} />
  )
}

Hasta ahora no hemos implementado nada de SSR o SSG. No es una de las razones por las cuales usar Next???

compas. Les paso como solucione el tema de los VALUE del SELECT por si les sirve.
coloquen el siguiente codigo y les va a andar.

  const categorySelectRef = useRef(null);
  useEffect(() => {
    categorySelectRef.current.value = product.categoryId;
  }, [product]);

No se olviden de poner el ref al
SELECT.

<select
                ref={categorySelectRef}
                id="category"
                name="category"
                autoComplete="category-name"
                className="..."
              >
	...
</select>

RECUERDEN QUE ES UNA MALA PRACTICA MANIPULAR DIRECTAMENTE EL DOM CUANDO SE USE REACT.

A meterle que si se puede!!👍😁

Solo quiero mencionar que en el form deben cambiar Toys por Shoes, ya que en la API es Shoes no Toys

              <select
                id="category"
                value={product?.category?.id.toString()}
                name="category"
                autoComplete="category-name"
                className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
              >
                <option value="1">Clothes</option>
                <option value="2">Electronics</option>
                <option value="3">Furniture</option>
                <option value="4">Shoes</option>
                <option value="5">Others</option>
              </select>
            </div>