No tienes acceso a esta clase

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

Aprende todo un fin de semana sin pagar una suscripción 🔥

Aprende todo un fin de semana sin pagar una suscripción 🔥

Regístrate

Comienza en:

2D
13H
47M
51S
Curso Práctico de React.js

Curso Práctico de React.js

Oscar Barajas Tavares

Oscar Barajas Tavares

Custom Hooks para la tienda

20/30
Recursos

Aportes 34

Preguntas 12

Ordenar por:

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

o inicia sesión.

Si estás aprendiendo React no es recomendable que uses el autocompletado de VSC o Github Copilot, permitele a tu memoria muscular adaptarse a la sintaxis y lógica, luego que lo domines a la perfección siente libre de usarlos para ahorrar tiempo de escritura.

Recomiendo añadirle un estado de “loading” al custom hook:

export default function useGetProducts(API) {
  const [products, setProducts] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    fetch(API)
      .then((res) => res.json())
      .then((response) => {
        setProducts(response);
        setIsLoading(false);
      });
  }, []);

  return { products, isLoading };
}

También se le podría añadir un estado de error 🤔

En React, podemos crear hooks por nuestra propia cuenta, donde nosotros podemos escribir toda la funcionalidad que deseamos. Ahora, haremos un hook el cual servirá para realizar la petición a todos los productos y traer su precio, imágen y descripción.

//useGetProducts.js
import { useEffect, useState } from 'react';
import axios from 'axios';

const useGetProducts = (API) => {
    const [products, setProducts] = useState([]);

	useEffect(async () => {
		const response = await axios(API);
		setProducts(response.data);
	}, [])

    return products;
}

export default useGetProducts;

El hook es muy sencillo. En el, creamos una array llamado products. Después con ayuda de useEffect realizamos una solicitud a una API (que es pasada como argumento), para traernos toda la información y guardarla con ayuda de axios. setProducts (de useState) guarda el response. Al final regresamos products.

Para poder usar el custom hook, lo implementamos en ProductList

// ProductList.jsx
import useGetProducts from '@hooks/useGetProducts'; // Lo importamos

const API = 'https://api.escuelajs.co/api/v1/products';

const ProductList = () => {
	const products = useGetProducts(API);

	return (
		<section className="main-container">
			<div className="ProductList">
				{products.map(product => (
					<ProductItem product={product} key={product.id}/>
				))}
			</div>
		</section>
	);
}

Ahora, creamos una constante llamada products que será el mismo array el cual contiene toda la información de los productos. En el, le pasamos API que será un argumento del hook. Como ya sabemos, más abajo en el div, usamos el método map para el array, en donde por cada producto creará una etiqueta del componente ProductItem. ProductItem recibe como datos un key, que es igual a product. id y también product que es igual al producto del array.

Para poder aprovechar esta información, editamos ProductItem.

import React, { useState } from 'react';
import '@styles/ProductItem.scss';
import buttonAddCart from '@icons/bt_add_to_cart.svg'

const ProductItem = ({product}) => {
	const [cart, setCart] = useState([]);

	const handleClick = () => {
		setCart([]);
	}

	return (
		<div className="ProductItem">
			<img src={product.images[0]} alt={product.title} />
			<div className="product-info">
				<div>
					<p>${product.price}</p>
					<p>{product.title}</p>
				</div>
				<figure onClick={handleClick}>
					<img src={buttonAddCart} alt="" />
				</figure>
			</div>
		</div>
	);
}

export default ProductItem;

En product item, recibimos estos datos en argumentos de la función dentro de llaves. Más abajo, en img, alt, y p, usamos las características de cada producto que son la imágen, titulo, descripción y precio. Así, podemos mostrar la información que corresponde a cada una.

Les comparto ambas formas para traer datos de una API:

  • Fetch (nativa), y con axios.

Ademas de como manejar el ciclo de vida cuando el componente va a desmontarse y el request de los datos no quede en el aire, sino que aborte dicho request.

const useGetProducts = () => {
	const [products, setProducts] = useState([]);
	const [loading, setLoading] = useState(true);
	const API = 'https://api.escuelajs.co/api/v1/products?limit=12&offset=0';

	// With fetch API
	useEffect(async () => {
      	const controller = new AbortController();
	    const { signal } = controller;
      
		try {
			const response = await fetch(API, { signal });
			const data = await response.json();

			if (data) {
				setProducts([...products, ...data]);
				setLoading(false);
			}
		} catch (err) {
			console.log('Error: ', err);
		}

		return () => controller.abort();
	}, []);

	// With axios
	useEffect(async () => {
		const cancelToken = axios.CancelToken;
		const source = cancelToken.source();

		try {
			const { data } = await axios(API, { cancelToken: source.token });

			if (data) {
				setProducts([...products, ...data]);
				setLoading(false);
			}
		} catch (err) {
			if (axios.isCancel(err)) {
				return 'axios request cancelled';
			}
			console.log('Error: ', err);
		}

		return () => source.cancel('axios request cancelled');
	}, []);

	return { products, loading };
};


cancelling-requests-with-fetch-or-axios

Para no estar repitiendo la palabra product (product.price, product.title) se puede hacer uso de la desestructuración con cada uno de los atributos

  const { id, title, price, images } = product;

Recomendacion: no usar async/await como parámetro en useEffect, aqui un ejemplo para modificarlo
function Example() {
const [data, dataSet] = useState<any>(null)

useEffect(() => {
async function fetchMyAPI() {
let response = await fetch(‘api/data’)
response = await response.json()
dataSet(response)
}

fetchMyAPI()

}, [])

return <div>{JSON.stringify(data)}</div>
}

Hola chicos por si a alguno le sucede que al momento de poner la foto le da un error de que no existe la propiedad [0], la solución es:

  • product.images?.[0],

al parecer como es asíncrono al momento de renderizar no encuentra datos sino hasta que se resuelve toda la petición por ende debemos utilizar el signo de exclamación en la propiedad images, este va a renderizar el valor solo cuando images exista dentro de product

En mi caso me gusta esta forma para desestructurar los props de react

  const { images, title, price } = product;

en la mad… ahora entiendo todo lo que te dice de hooks en teoria son funciones o clases creadas en modulos, que te explico el profe que no se le entiende cuando habla… jaja gracias a los que me dijeron que viera dos veces el tutorial de JS profesional

Para el fetching de datos pueden utilizar algo nativo como fetch(), ya que así se libraran de librerías extras en su proyecto.

  const [products, setProducts] = useState([]);

  const API = "https://api.escuelajs.co/api/v1/products";
  const fetchApi = async () => {
    await fetch(API)
      .then((response) => response.json())
      .then((data) => setProducts(data));
  };

  useEffect(() => {
    fetchApi();
  }, []);

Sale un error en la consola por el uso de async dentro del useEffect(), se corrige así:

<import { useEffect, useState } from "react";
import axios from "axios";

const useGetProducts = (API) => {

  const [products, setProducts] = useState([]);

//se saca el fecth de los datos del UseEffec
  async function fetchData() {
    const response = await axios(API);
    setProducts(response.data);
  }

  useEffect(() => {
    fetchData();
  }, []);

  return products;
};

export default useGetProducts;> 

Hola chicos! las últimas versiones de React te dan un error cuando colocas el async directamente en el useEffect:

	useEffect(async()=>{
		const response = await axios(API);
		setPorducts(response.data);
	}, [])

para solucionarlo, debes crear otra función asíncrona y luego llamarla DENTRO del bloque del useEffect:

	useEffect(()=>{
		async function fetchData(){
			const response = await axios(API);
			setPorducts(response.data);
		}		
		fetchData()
	}, [])

Támbien Puedes crear la función fuera del bloque y luego llamarla dentro:

	async function fetchData(){
		const response = await axios(API);
		setPorducts(response.data);
	}		

	useEffect(()=>{
		fetchData()
	}, [])

Espero tengan un gran dia! el curso esta increíble!! ❤️

Gente, por cierto, si no quieren usar axios pueden usar fetch tranquilamente:

import { useEffect, useState } from "react";

const useGetProducts = (API) => {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch(API)
      const data = await response.json()
      setProducts(data)
    }
    fetchData()
  }, [])
  return products
}

export { useGetProducts }

No me aparecían los datos de la api pero si me hacia el mapeo
Mi error fue no poner los corchetes al llamar product, no me percate de eso y estuve batallando una hora con eso

Comparto por si a alguien le pasa algo similar 😅
Ya funcionando este fue uno de los artículos que mas me dio risa

mi practica:

import React from 'react';
import ProductItem from '@components/ProductItem';
import '@styles/ProductList.scss';
import useGetProducts from "@hooks/useGetProducts";

const API = 'https://api.escuelajs.co/api/v1/products';

const ProductList = () => {
    const products = useGetProducts(API);
    return (
        <section className="main-container">
            <div className="ProductList">
                {products.map(product => (
                    <ProductItem key={product.id} product={product}/>
                ))}
            </div>
        </section>
    );
}

export default ProductList;

import {useEffect, useState} from 'react';
import axios from "axios";

const useGetProducts = (API) => {
    const [products, setProducts] = useState([]);
    useEffect(async () => {
        const response = await axios.get(API + '?limit=15&offset=0');
        setProducts(response.data);
    }, []);
    return products;
}

export default useGetProducts;
import React, {useState} from 'react';
import '@styles/ProductItem.scss';
import btAddToCart from '@icons/bt_add_to_cart.svg';

const ProductItem = ({product}) => {
    const {id, title, price, images} = product;
	const [cart, setCart] = useState([]);
	const addToCart = () => {
		setCart([]);
	}
    return (
        <div className="ProductItem">
            <img
                src={images[0]}
                alt={title}/>
            <div className="product-info">
                <div>
                    <p>${price}</p>
                    <p>{title}</p>
                </div>
                <figure onClick={addToCart}>
                    <img src={btAddToCart} alt=""/>
                </figure>
            </div>
        </div>
    );
}

export default ProductItem;

Una forma de darle formato al precio de nuestro productos.

<p>
	{ new Intl.NumberFormat('en-US', 
		{ 
			style: 'currency', 
			currency: 'USD'
		}
	).format(product.price)}
</p> 

Me distraje viendo si podía hacer un filtrado y que solo tomara la cantidad de fotos que tu quieras, ya que lo hice, lo comparto.

Otra forma como podemos completar la key,
es con la opción: index, que tiene el .map como segundo argumento de la función callback

key={index}

parceros tengo un problema, no me cargan las imágenes, los títulos y precios si, pero las imágenes no, solo me aparecen las ultimas 3 imágenes y en consola me sale:
Failed to load resource: the server responded with a status of 404 (Not Found)

watch:1 Failed to load resource: the server responded with a status of 502 ()

me aparecen todas las imágenes con ese error 502, agradezco si alguien puede ayudarme, llevo una hora y media dándole vueltas y no encuentro solución 😦

Hice este hook para dar el formato de pesos colombianos

<code> 
const useNumberFormat = ({
  value = 0,
  locales = 'es-CO',
  style = 'currency',
  currency = 'COP',
} = {}) => {
  const numberFormat = new Intl.NumberFormat(locales, { style, currency }).format(value)

  return { numberFormat }
}

export {
  useNumberFormat
}

podemos usar la destructuracion en nuestro productItem, asi sabemos en el inicio de nuestro componente cuales son los valores que estamos usando, y ademas mejoramos la legibilidad de nuestro codigo al no estar usando recurrentemente la palabra product.propiedad

❤️

Algo que me agrada bastante de los custom hooks es la versatilidad de hacer lógica compleja en un archivo y en el jsx tener prácticamente sólo UI, lamentablemente en la industria hay papiros atascados de lógica sin orden con UI anidad, valoren esta clase es ORO

Estoy notando que en la api actualmente ya no tiene url en el array de images, por lo que me parece que es la razón por la cual ya no renderiza las imágenes.

AVISO
En el index.js de nuestra aplicación deberíamos crear la aplicación en las nuevas versiones de React de la siguiente manera:

import React from "react";
import { createRoot } from "react-dom/client";
import App from "./routes/App.jsx";

const container = document.getElementById("app");
const root = createRoot(container);

root.render(<App />);

Madre mía con el Copilot de github… ajja

La consola da un warning con el useEffect y recomienda usarlo de la siguiente manera

useEffect(() => {
    async function fetchData() {
      const response = await axios(API);
      setProducts(response.data);  
    }
    fetchData();
  }, []);

Title se pronuncia como ˈtīdl

En la parte de Product item se puede hacer destructuración del objeto product para hacer el código mas legible:

const ProductItem = ({ product }) => {
	const { title, price, images } = product;
	...
  return (
    <div className="ProductItem">
      <img src={images[0]} alt={title} />
      <div className="product-info">
        <div>
          <p>${price}</p>
          <p>{title}</p>
        </div>
        <figure onClick={handleClick}>
          <img src={addCart} alt="" />
        </figure>
      </div>
    </div>
  );
};

este profesor va mas rápido que mejor dicho cójanlo si pueden

Yo no veo un pollo xd

Tambien podemos coger la imagen que esta destinada a la categoria, porque si se fijan, en el API tenemos un objeto que tiene un key de nombre “category” entonces ese category tiene la imagen del producto directamente. de ahi podemos coger la imagen.

{
    "id": 1,
    "title": "Small Granite Chicken",
    "price": 810,
    "description": "New range of formal shirts are designed keeping you in mind. With fits and styling that will make you stand apart",
    "category": {
      "id": 3,
      "name": "Furniture",
      "image": "https://placeimg.com/640/480/any?r=0.5210008225163332"
    },
    "images": [
      "https://placeimg.com/640/480/any?r=0.006060555121223832",
      "https://placeimg.com/640/480/any?r=0.1439081209827473",
      "https://placeimg.com/640/480/any?r=0.5621224244775178"
    ]
  }

El codigo cogiendo la imagen desde category quedaria algo asi similar. fijense bien que aqui tenemos src{product.category.image} en vez de
src{product.image[0]} que seria lo mismo pero en el segundo caso estamos cogiendo la primera imagen del arreglo de imagenes que tenemos en nuestro objeto.

const ProductItem = ({product}) => {
  const [cart, setCart] = useState([])
  const handleClick = () =>{
    setCart([])
  }
  return (
    <div className="ProductItem">
      <img
        src={product.category.image}
        alt={product.title}
      />
      <div className="product-info">
        <div>
          <p>${product.price}</p>
          <p>{product.title}</p>
        </div>
        <figure onClick={handleClick}>
          <img src={AddToCart} alt="add cart" />
        </figure>
      </div>
    </div>
  );
};

el segundo argumento que recibe useEffect que ahora solo lo colocamos como un array vacio, hace referencia a los elementos de los que dependera el renderizado o ejecucion de esta misma funcion (useEffect), ejemplo si nuestro llamado a la api depende de un evento que sea ejecutado con un onclick, ese mismo elemento es el que debemos pasar en el array hasta ahora vacio, asi garantizamos que el useEffect solo se ejecutara cuando ese elemento cambie su valor

Las imagenes me dieron miedo perdon