Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Curso de Next.js

Curso de Next.js

Jonathan Alvarez

Jonathan Alvarez

Creando y consumiendo nuestra propia API

11/27
Recursos

Aportes 53

Preguntas 14

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Para los que usan windows y no pueden levantar el inspect, pueden agregar este script en el package.json

{
...
"dev:inspect": "npm --node-options=--inspect=0.0.0.0:9229 run dev"
...
}

Challenge Completed!

import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {
  // state
  const [product, setProduct] = useState<TProduct>()
  // router
  const {
    query: { id },
  } = useRouter()

  useEffect(() => {
    if (id) {
      window
        .fetch(`/api/avo/${id}`)
        .then((res) => res.json())
        .then((data) => setProduct(data))
    }
  }, [id])

  return (
    <section>
      <h1>Página producto: {product?.name} </h1>
    </section>
  )
}

export default ProductPage

Si cuando llaman a una API el JSON se les muestra crudo y muy dificil de leer, AQUÍ les dejo una extensión para Chrome que les permite formatear y ver mucho mejor las API’s

👨‍💻 Parámetros en API y Debug en Next.js

<h4>Apuntes</h4>

Si deseamos recibir parámetros en nuestra API, al igual como generamos nuestra ruta dinámica debemos crear un archivo [parámetro].[js | ts]

Para hacer debugging en Next.js cuando corremos nuestro entorno en development debemos correr el siguiente script

"scripts": {
    "dev": "NODE_OPTIONS='--inspect' next",
		...
},

una vez que este corriendo podemos acceder al debugger en el navegador en la ruta:

about:inspect

import { NextApiRequest, NextApiResponse } from "next";

const allAvos = async (request: NextApiRequest, response: NextApiResponse) => {
  const id = request.query.id

	// ANTES
	// response.statusCode = 200; // ok
  // response.setHeader('Content-type', 'application/json')
  // response.end(JSON.stringify(entry)

  response.status(200).json(entry)
}

export default allAvos;

Next.js nos ofrece una mejora del paquete nativo de Node.js HTTP el cual nos da la posibilidad de usar la API de Next.js a través de un objecto mejorado ademas que nos facilita al momento de enviar los datos al cliente.


Para poder consumir la API podemos hacerlo como si fuera un componente común y corriente de react

import React, { useState, useEffect } from 'react'

const HomePage = () => {
  const [productList, setProductList] = useState([])

  useEffect(() => {
    // Especificamos que usaremos window
		window
      .fetch('/api/avo')
      .then((response) => response.json())
      .then(({ data, length }) => {
        setProductList(data)
      })
  }, [])

  return (...)
}

RESUMEN: Next.js nos ofrece debugging de una manera simple ademas de darnos una mejora en las funcionalidades de HTTP y crear entry points de una manera rápida e eficiente

Se ve muy profesional y ordenado con TS, me gustarían un curso completo de TS con React y Hooks

Reto cumplido 😃

Hola Devs:
Mi solucion:

PSD: Tuve que recurrir a resolver un error que tenia en el API con los responses
Recuerden, #NuncaParenDeAprender 💚

No entiendo como todos publican su codigo sin probarlo, el profe mando a hacer algo que no se puede hacer aun xD

Model:

import React, { FunctionComponent } from 'react'
import Typography from 'components/Typography'

interface Props {
  id: string,
  image?: string,
  name: string,
  price: number,
  sku: string,
  attributes?: Attributes,
}

interface Attributes {
  description: string,
  hardiness?: string,
  shape: string,
  taste: string,
}

export const Product: FunctionComponent<Props> = (product: Props) => {
  return (
    <div className="container">
      <Typography>Image: {product?.image}</Typography>
      <Typography>Name: {product?.name}</Typography>
      <Typography>Price: {product?.price}</Typography>
      <Typography>Sku: {product?.sku}</Typography>
      <style jsx>{`
        .container {
          margin: 30px 0;
        }
      `}</style>
    </div>
  )
}

export default Product

HomePage

import React, { useState, useEffect, FunctionComponent } from 'react'
import Navbar from '../components/Navbar'
import Product from 'components/Product'

const HomePage: FunctionComponent = () => {
  const [products, setProducts] = useState<TProduct[]>([])

  useEffect(async () => {
    try {
      const response = await window.fetch('api/avo')
      const { data } = await response.json();
      setProducts(data);
    } catch (error) {
      setProducts([]);
    }

  }, [])

  return (
    <>
      <Navbar />
      <div className="main">
        <h1>Next.js!</h1>
        {products?.map((product, key) => (
          <div key={key}>
            <Product id={product.id} name={product.name} price={product.price} sku={product.sku} />
          </div>
        ))}
        <style jsx>{`
        .main {
          padding: 10px;
        }
      `}</style>
      </div>
    </>
  )
}

export default HomePage

Se puede dividir mas, como por ejemplo ese fetch hacerlo desde un servicio, lo que nose si con next js es necesario implementar alguna store como redux, yo creo que si pero tengo esa duda

Esto me funciono en Windows (y)

Instalar CrossEnv

npm i cross-env

Modificar Package.json

    "dev": "cross-env NODE_OPTIONS='--inspect' next",

Mi código.

Por si alguien más le sucede: Si obtienen el error: SyntaxError: Unexpected end of JSON input es por que es necesario un catch en la resolución de las promesas.

Aquí mi código

import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {

  const [product, setProduct] = useState<TProduct>()

  const { query: { id } } = useRouter()  

  useEffect(() => {
    window
      .fetch(`/api/avo/${id}`)
      .then( (response) => response.json())
      .then( prod => {
        setProduct(prod)
      } )
      .catch( err => console.log(err) )
  },[id])
  

  return (
    <section>
      <h1>Página producto: {product?.name}</h1>
    </section>
  )
}

export default ProductPage

Aquí el error en caso de no agregar un catch

Para los que usan windows

{
   "dev": "set NODE_OPTIONS=\"--inspect\" && next"
}

Así es como se setean las variables, salu2

así iria la solución al reto, importante tener encuenta que apenas se carga la página el valor de id no está disponible entonces se usa el useEffect de esta forma para que se ejecute el llamado al api apenas está disponible

useEffect(() => {
    //....
  }, [query.id])
import { useRouter } from 'next/router'

const ProductPage = () => {
  const { query } = useRouter()

  const [product, setProduct] = useState<TProduct>()
  useEffect(() => {
    window
      .fetch(`/api/avo/${query.id}`)
      .then((response) => response.json())
      .then((reponse) => {
        setProduct(reponse)
      })
  }, [query.id])

  return (
    <section>
      <h1>Página producto: {query.id}</h1>
      <div>Nombre: {product?.name}</div>
    </section>
  )
}

export default ProductPage

Reto 🚀:

import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import Navbar from '../../components/Navbar/Navbar'

const ProductPage = () => {
  const [product, setProduct] = useState<TProduct[]>([])

  const { query } = useRouter()
  useEffect(() => {
    if (query.id) {
      window
        .fetch(`/api/avo/${query.id}`)
        .then((response) => response.json())
        .then((data) => setProduct(data))
    }
  }, [query])
  console.log(product)

  return (
    <section>
      <Navbar />
      <h1>Product page: </h1>
      <div>{product.name}</div>
    </section>
  )
}

export default ProductPage

import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import data from "database/data";
import Error from "../error";
const ProductItem = () => {
  const {
    query: { id },
  } = useRouter();

  const [product, setProduct] = useState<TProduct>([]);
  useEffect(() => {
    if (id !== "" || undefined) {
      window
        .fetch(`/api/avo/${id}`)
        .then((response) => response.json())
        .then((data) => setProduct(data))
        .catch((error) => console.error(error)); 
    }
  }, [id]);

  console.log();

  return (
    <div>
      <h1>Pagina del producto</h1>
      <h3>{product.name}</h3>
    </div>
  );
};

export default ProductItem;

Desconosco si lo realize con exito jjajaj pero aqui esta mi propuesta.
Todo lo realice desde la pagina que ya habiamos creado [productID] ya que era la que nos dejaba poner el id en el url.

Problemas: si refrescas te dice que no encuentra los .name o .description, tienes que borrar esa parte del código guardar, poner otravez esa parte que quitaste y listo ya los pone

codigo

import react from "react";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";

const ProductItem = () => {
  const [productItem, setProductItem] = useState([]);

  // Utilizando hook para obtener el id que viene del url
  // en este caso es el productID
  const {
    query: { productID },
  } = useRouter();

  useEffect(() => {
    window
      .fetch(`/api/avo/${productID}`)
      .then((response) => response.json())
      .then((data) => {
        setProductItem(data);
      });
  }, [productID]);

  return (
    <div>
      <p>Esta es la pagina del producto: {productID}</p>
      <p>{productItem.name}</p>
      <p>{productItem.price}</p>
      <p>{productItem.attributes.description}</p>
    </div>
  );
};

export default ProductItem;

// "dev": "NODE_OPTIONS='--inspect=0.0.0.0:9229' next", // luego puedo ir a about:inspect y lo depuro como una app mobile. !!!!!

Para poder correr la depuración en Windows, la documentación de NEXTjs recomienda usar el paquete:

https://www.npmjs.com/package/cross-env

Luego crear el script:

"dev": "cross-env NODE_OPTIONS='--inspect' next dev",

Como mexicano me sangran los oidos cada vez que escucho que le dice al AGUACATE avocado 🤣🤣🤣🤣🤣🤣

<import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {
  const { query } = useRouter()

  const [product, setProduct] = useState<TProduct>()

  const productId = query.id;
  useEffect(() => {
    window
    .fetch(`/api/avo/${productId}`)
    .then(response => response.json()
    .then((data) => {
      setProduct(data)
    }))
  }, [productId])


  return (
    <section>
      <h1>Página producto</h1>
      { product ?
      <div>
      <h3>{product.name}</h3>
      <h3>{product.price}</h3>
      <h3>{product.sku}</h3>
      <img src={product.image} />
      <p>{product.attributes.description}</p>
      <p>{product.attributes.hardiness}</p>
      <p>{product.attributes.shape}</p>
      <p>{product.attributes.taste}</p>
      </div>
      : '' }
    </section>
  )
}

export default ProductPage
> 

Podrías lanzar una excepción para evitar castear el id como string.

Ejemplo:

if ('string' !== typeof id) {
 throw new Error('El id debe ser de tipo string.');
}

// y ya puedes estar seguro que si pasa el if es porque id es de tipo string
const entry = await db.getById(id);

Para los que tuvieron el siguiente fallo en hacer debuging

$ yarn dev
yarn run v1.22.17
warning ..\..\..\..\..\..\..\package.json: No license field
$ NODE_OPTIONS='--inspect' next dev
'NODE_OPTIONS' is not recognized as an internal or external command,
operable program or batch file.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Al parecer no acepta el comando que el profesor puso, así que tuve que investigar
y era que falta un paquete llamado cross-env , así que lo instalamos

npm i cross-env

Ahora vamos al package.json y modificamos ligeramente el script

"scripts": {
    "dev": "cross-env NODE_OPTIONS='--inspect' next dev",
...
}

Le agregamos el dev al final

para mas información anexo los links donde saque la informacion:
https://nextjs.org/docs/advanced-features/debugging
https://stackoverflow.com/questions/53948521/node-options-is-not-recognized-as-an-internal-or-external-command

Mi solución al reto

import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {
  const { query } = useRouter()
  const [ avocado, setAvocado ] = useState<TProduct>({} as TProduct)
  useEffect(() => {
    window.fetch(`/api/avo/${query.id}`)
    .then(response => response.json())
    .then(({data}) => setAvocado(data))
  }, [query.id])

  return (
    <section>
      <h1>Página producto: {avocado.name}</h1>
    </section>
  )
}

export default ProductPage

Aca esta la solución al reto de la clase

import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';

const ProductPage = () => {
  const [product, setProduct] = useState<TProduct>();
  const { query } = useRouter();
  const { id } = query;

  useEffect(() => {
    (id) &&
    window.fetch(`/api/avo/${id}`)
      .then(response => response.json())
      .then(data => setProduct(data))
      .catch(error => console.error(error.message));
  }, [query])

  // console.log(product);
  

  return (
    <section>
      {product
      ?
        <div>
          <h1>Página producto: {id}</h1>
          <h2>{product?.name}</h2>
        </div>
      :
        <p>Producto no encontrado</p>
      }
    </section>
  )
}

export default ProductPage;

Mi solución:

import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import Navbar from 'components/Navbar/Navbar'

const ProductPage = () => {
  const [product, setProduct] = useState<TProduct>()
  const router = useRouter()
  const { id } = router.query

  useEffect(() => {
    if (id) {
      fetch(`/api/avo/${id}`)
        .then((respose) => respose.json())
        .then((data) => setProduct(data))
        .catch((error) => console.error(error.message))
    }
  }, [id])

  if (!product) {
    return <div>Cargando...</div>
  }

  const { name, id: idProduct, sku, image } = product as TProduct

  return (
    <section>
      <Navbar />
      <div>
        <h1>{name}</h1>
        <p>{idProduct}</p>
        <p>{sku}</p>
        <p>{image}</p>
      </div>
    </section>
  )
}

export default ProductPage

En el caso de la conexion con un solo avocado, creo que seria buena idea hacer la comprobacion de que en la url se le este pasando un valor que si exista. A lo que me refiero es que creo que deberia haber una comprobacion de que el valor que se le pasa a la url exista en la “base de datos”. Una manera en la que se puede comprobar esto es:

if(productList[0]){
    let included = productList.find(item => item.id == router.query.id);
    console.log(included)
}

En donde verificamos que el router.query.id este en la productList. Si included es undefined, procedemos a renderizar el home, si no es asi, renderizamos la pagina del avocado individual

  useEffect(() => {
    window
      .fetch(`/api/avo/${query.id}`)
      .then((response) => response.json())
      .then((avo) => {
        setProduct(avo)
      })
  }, [query.id])

No entiendo de donde sale el TProduct, ¿alguien me ayuda?

<const ProductPage = () => {
  const { query: { id } } = useRouter()
  const [product, setProduct] = useState<TProduct>();
  const [isLoading, setIsLoading] = useState<boolean>(false);

  useEffect(() => {
    setIsLoading(true);
    window.fetch(`/api/avo/${id}`)
      .then((res) => res.json())
      .then((res) => {
        setProduct(res);
        setIsLoading(false);
      })
      .catch((err) => console.log(err, 'error'))

  }, [id])

  return (
    <section>
      {isLoading ? (
        <span>Cargando...</span>
      ) : (
        <div>
          <h1>Nombre del producto: {product?.name}</h1>
          <p>{product?.attributes.description}</p>
        </div>
      )}
    </section>
  )
}> 

Al colocar:

"dev": "npm --node-options=--inspect=0.0.0.0:9229 run dev"

Me arroja el siguiente error:

Starting inspector on 127.0.0.1:9229 failed: address already in use

¿Alguien sabe por qué?

Le agregué un loading y mayor definición de types con typescript 😃

import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";

const ProductItem = () => {
  const {
    query: { id }
  } = useRouter();

  const [product, setProduct] = useState<TProduct>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    if (!!id) {
      window
        .fetch(`/api/avo/${id}`)
        .then((res): Promise<TProduct> => res.json() as Promise<TProduct>)
        .then(p => setProduct(p))
        .catch(console.log)
        .finally(() => setLoading(false));
    }
  }, [id]);

  if (loading) {
    return "Loading page...";
  }

  return (
    <div>
      El nombre del producto con id {id} es {product?.name}
    </div>
  );
};

export default ProductItem;

Mi reto:

import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

const ProductPage = () => {
  const { query } = useRouter();
  const id = query && query.id;

  const [product, setProduct] = useState();

  useEffect(() => {
    window.fetch(`/api/avo/${id}`)
      .then(res => {
        return res.json()
      }).then(json => {
        console.log(json)
        setProduct(json);  
      }).catch(console.log);
  }, [id]);

  return (
    <section>
      <h1>{product?.name}</h1>
      <p>{product?.attributes?.description}</p>
      <img src={product?.image}></img>
    </section>
  )
}

export default ProductPage```

IMPORTANTE tener un optional chaining o un condicional para evitar que la aplicacion se rompa sin aun no esta disponible el valor de 'product'

Mi solución
index.tsx

{
        productList.map(product => (
          <p>
            <Link href={`/product/${product.id}`}>
              {product.name}
            </Link>
          </p>
          
        )) 
      }

product/[id].tsx

import React from 'react'
import { useRouter } from 'next/router'
import Navbar from 'components/navbar/Navbar'

const ProductPage = () => {
  const { query: {id} } = useRouter()

  const[product, setProduct] = React.useState<TProduct>()

  React.useEffect(()=>{
    (id) && window
      .fetch(`/api/avo/${id}`)
      .then(res => res.json())
      .then(data => setProduct(data))
      .catch(error=>console.error(error.message))
  }, [id])

  return (
    <>
      <Navbar />
      <section>
        <h1>{product?.name}</h1>
        <p>{product?.attributes.description}</p>
        <p>Precio: {product?.price}</p>
        <p>Forma: {product?.attributes.shape}</p>
      </section>
    </>
  )
}

export default ProductPage

Aqui mi reto:

import { useRouter } from 'next/router'
import { useState, useEffect } from 'react'
import Navbar from '../../component/Navbar/Navbar'

const ProductItem = () => {
  const { query: {id} } = useRouter();
  const [product, setProduct] = useState<TProduct>()
  console.log(id)
  useEffect(() => {
    if (id) {
      window
        .fetch(`/api/avo/${id}`)
        .then((res) => res.json())
        .then((data) => setProduct(data))
        .catch((error) => console.error(error))
    }   
  }, [id])
    
  return ( 
    <>
      <Navbar/>
      <h1>
        Esta es la página del producto: 
      </h1> 
      <h3>{product?.name}</h3>
      <img src={product?.image} alt={product?.name} />
      <p>{product?.attributes.description}</p>
      {/* producto: {router.query.id} */}
      {/* .id viene del nobmre que le hemos puesto al archivo */}
    </>
   );
}
export default ProductItem;```

Aquí mi página del producto

import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {
  const { query } = useRouter()
  const [product, setProduct] = useState<TProduct>()

  useEffect(() => {
    window
      .fetch(`/api/avo/${query.id}`)
      .then((response) => response.json())
      .then((json) => setProduct(json))
  }, [query.id])

  return (
    <section>
      <h1>{product?.name}</h1>
      <h3>SKU: {product?.sku}</h3>
      <img src={product?.image} alt={product?.name} />
      <p>Price: {product?.price}</p>
    </section>
  )
}

export default ProductPage

Y acá como acceso al producto de mi preferencia desde el Home

import React, { useState, useEffect } from 'react'
import Navbar from '../components/Navbar/Navbar'
import Link from 'next/link'

const HomePage = () => {
  const [productList, setProductList] = useState<TProduct[]>([])

  useEffect(() => {
    window
      .fetch('/api/avo')
      .then((response) => response.json())
      .then(({ length, data }) => {
        setProductList(data)
      })
  }, [])

  return (
    <div>
      <Navbar />
      <div>Platzi and Next.js!</div>
      {productList.map((product) => (
        <div>
          <Link href={`/product/${product.id}`}>{product.name}</Link>
        </div>
      ))}
    </div>
  )
}

export default HomePage

Mi solución fue esta:

import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/router'

const Product = () => {
  const { query: { id }} = useRouter()
  const [product, setProduct] = useState<TProduct>()
  const [displayError, setDisplayError] = useState<boolean>(false)

  useEffect(() => {
    if (id) {
      window.fetch(`/api/avo/${id}`)
        .then(response => {
          if (response.ok) {
            return response.json()
          } else {
            throw 'not found?'
          }
        })
        .then(data => setProduct(data))
        .catch(_error => setDisplayError(true))
    }
  }, [id])

  return (
    <section>
      {displayError
      ? <h1>Not Found!</h1>
      : (
        <>
          <span>Name: {product?.name}</span>
        </>
      )}
    </section>
  )
}

export default Product

Y para retornar un error si no se encuentra el id, el archivo de la api:

import { NextApiRequest, NextApiResponse } from 'next'
import DB from '@database';

const allAvos = async (request: NextApiRequest, response: NextApiResponse) => {
  const db: DB = new DB()
  const id: string = request.query.id as string

  const entry: any = await db.getById(id)

  if (entry) {
    response.status(200).json(entry)
  } else {
    response.status(404).json({})
  }
}

export default allAvos

Hola, tengo un error, será que alguien puede ayudarme?

Syntax error: Unexpected token, expected ","

  2 | import DB from "@database";
  3 | 
> 4 | const AllAvos = async (request : NextApiRequest, response : NextApiResponse) => {
    |                                ^
  5 |     const db = new DB()
  6 | 
  7 |     const id = request.query.id

Mi código está así:

import { NextApiRequest, NextApiResponse } from 'next';
import DB from "@database";

const AllAvos = async (request : NextApiRequest, response : NextApiResponse) => {
    const db = new DB()

    const id = request.query.id

    const allEntries = await db.getAll()
    const length = allEntries.length

    response.statusCode = 200 
    response.setHeader( 'Content-type', 'application/json' )
    response.end(JSON.stringify({ data: id }))
}

export default AllAvos ```

Challenge finished…

import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {
  // Routing
  const { 
    query: { id },
  } = useRouter()

  //Handlinh state
  const [product, setProduct] = useState<TProduct>([]);
 


  useEffect(() => {
    if (id) {
      window
        .fetch(`/api/avo/${id}`)
        .then((response) => response.json())
        .then((data) => setProduct(data))
      }
    }, [id])

    //Rendering JSX
  return (
    <section>
      <h1>Este producto es: {product.name}</h1>
      <picture><img src={product.image} alt={product.name}/></picture>
    </section>
  )
}

export default ProductPage```

Si nos conectamos a una API en otro servidor o lenguaje no demos hacer lo de /api/avo sino directamente al api del otro server? en el window.fetch verdad

Hola! estoy muy bloqueada… intente hacer el challenge pero me da un error SyntaxError: Unexpected end of JSON input
Sera que alguien podra ayudarme por favor? Gracias!

Que cool es Next JS.
Aqui mi solución al challange

import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";

const ProductItem = () => {
  const [product, setProduct] = useState<TProduct>();
  const {
    query: { id },
  } = useRouter();

  const fetchProduct = async () => {
    if (id) {
      const response = await window.fetch(`/api/avo/${id}`);
      if (response.status === 200) {
        const product = await response.json();
        setProduct(product);
      }
    }
  };

  useEffect(() => {
    fetchProduct();
  }, [id]);

  return (
    <>
      <div>Id Producto: {id}</div>
      <div>Nombre: {product?.name}</div>
    </>
  );
};

export default ProductItem;

Hola, una pregunta. Alguien sabe cual es la extension en chrome que usa el profesor para visualizar el JSON formateado? Saludos

el reto:

import React, {useEffect, useState} from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {
  const [product, setProduct] = useState<TProduct>({
    id: '',
    name: '',
    sku: '',
    price: 0,
    image: '',
    attributes: {
      description: '',
      shape: '',
      hardiness: '',
      taste: '',
    }
  });
  const { query } = useRouter()

  useEffect(() => {
    window
      .fetch(`/api/avo/${query.id}`)
      .then(response => response.json())
      .then((data) => {
       setProduct(data);
      });
  });
  return (
    <section>
      <h1>Página producto: {product.id}</h1>
      <div>name: {product.name}</div>
    </section>
  )
}

export default ProductPage

¿Alguien podría decirme por qué coloca esto?

.then(console.log)

Hecho 😄

import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {

  const [product, setProduct] = useState<TProduct>()
  const { query: { id } } = useRouter()

  useEffect( () => {
    (id) && window.fetch(`/api/avo/${id}`)
    .then((response) => response.json())
    .then((data) => {
      setProduct(data)
      console.log("data", data)
    })
  }, [id])

  return (
    <section>
      <h1>Página producto: {id}</h1>
      <h3>{ product?.sku }</h3>
      <p>{ product?.name }</p>
    </section>
  )
}

export default ProductPage

simplemente me enamore de NEXT.js

Asi resolvi el challenge

import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";

const ProductItem = () => {
  const router = useRouter();
  const { productId } = router.query;
  const [product, setProduct] = useState({});
  useEffect(() => {
    window
      .fetch(`/api/avo/${productId}`)
      .then((res) => res.json())
      .then((data) => setProduct(data))
      .catch((err) => console.log(err));
  }, [productId]);

  return (
    <>
      <h2>Product page {product.name}</h2>
      <img src={product.image} />
    </>
  );
};

export default ProductItem;

Pueden usar la ruta anidada en la API o ponerla al mismo nivel y simplemente usar algo como swr y desde la pagina obtener el id y mandarlo como parametro

function fetcher(url: string) {
  return window.fetch(url).then((res) => res.json())
}

export function useEntry(id: string) {
  return useSWR(`/api/get-entry?id=${id}`, fetcher)
}
// asi iria en la ruta

 const id = router.query.id?.toString()
  const { data } = useEntry(id)
y ahi ya hicieron su peticion se ahorran el useEffect y no necesitan anidar la ruta

Reto completado!


Solo logre mostrar los datos en la consola

import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

const ProductPage = () => {
  const [product, setProduct] = useState<TProduct>()
  const {
    query: { id },
  } = useRouter()

  useEffect(() => {
    window
      .fetch(`/api/avo/${id}`)
      .then((res) => res.json())
      .then((data) => {
        setProduct(data)
        console.log(data)
      })
  }, [id])

  return (
    <div>
      <div>product</div>
      <div>{product?.name}</div>
    </div>
  )
}
export default ProductPage

Para los que no les funciona con el DevTools como a mi 😅, les comparto una forma sencilla para hacerlo desde VSCode:

Crear una carpeta .vscode y dentro un archivo launch.json con el siguiente código:

{ “version”: “0.2.0”, “configurations”: [ { “type”: “node”, “request”: “attach”, “name”: “Launch Program”, “skipFiles”: ["<node_internals>/**"], “port”: 9229 } ] }

y por ultimo darle **f5 ** para iniciar con modo debug. Ya con esto pueden poner sus breakpoints. 😁

PDTA: Deben de dejar en el package.json en el task runner dev el valor de “NODE_OPTIONS=’–inspect’ next dev” y iniciarlo.

No pude hacer funcionar el debug en Chrome Dev Tools, pero si en VS code. Aca les dejo la liga de la página oficial de Nextjs Doc.

Mi solución:

import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';

const ProductItem = () => {
  const {
    query: { id },
  } = useRouter();

  const [productItem, setProductItem] = useState({});

  useEffect(() => {
    id &&
      window
        .fetch(`/api/avo/${id}`)
        .then((response) => response.json())
        .then(setProductItem)
        .catch((err) => console.error(err.message));
  }, [id]);
  return (
    <div>
      <h1>{productItem?.name}</h1>
      <p>{productItem?.attributes.description}</p>
    </div>
  );
};

export default ProductItem;