reactjs - Typescript: errores de tipo al obtener API: "El objeto podría ser 'nulo'."
Estoy refactorizando mi aplicación React a TypeScript y hasta ahora todo bien. Estoy en mi último componente, el que obtiene los datos de la API, y estoy tratando de entender por qué estos errores de tipo no se resuelven. Usé https://jvilk.com/MakeTypes/ para ayudar a estructurar correctamente las interfaces.
El objeto podría ser 'nulo' dos veces para error y stories (líneas comentadas abajo)
El parámetro '___' tiene implícitamente un tipo 'any' dos veces para story y idx (líneas comentadas abajo)
Estoy teniendo problemas con este componente de llamada API. ¿Qué me está faltando?
import React, { FC, ReactElement, useEffect, useState } from "react";
import Story from "./Story";
export interface NewsProps {
results?: (ResultsEntity)[] | null;
}
export interface ResultsEntity {
section: string;
title: string;
abstract: string;
url: string;
multimedia?: (MultimediaEntity)[] | null;
}
export interface MultimediaEntity {
url: string;
caption: string;
}
const News: FC<NewsProps> = ({results:ResultsEntity}):ReactElement => {
const [error, setError] = useState(null);
const [stories, setStory] = useState(null);
useEffect(() => {
const getCurrentPage = () => {
const url = new URL(window.location.href);
const page = url.pathname.split("/").pop();
return page ? page : "home";
};
const section = getCurrentPage();
fetch(
`https://api.nytimes.com/svc/topstories/v2/${section}.json?api-key=4fzCTy6buRI5xtOkZzqo4FfEkzUVAJdr`
)
.then((res) => res.json())
.then((data) => {
setTimeout(() => setStory(data), 1500);
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, []);
if (error) {
return <div>Error: {error.message}</div>; /// "Object is possibly 'null'" (for error)
} else
if (!stories) {
return <div>Loading...</div>
} else {
return (
<>
<ul className="stories">
{stories.results.map((story, idx) => { // "Object is possibly 'null'" (for stories)
// Parameter '__' implicitly has an 'any' type (for idx & story)
return (
<Story
key={idx}
title={story.title}
abstract={story.abstract}
img={
story &&
story.multimedia &&
story.multimedia[0] &&
story.multimedia[0].url
? story.multimedia[0].url
: null
}
alt={
story &&
story.multimedia &&
story.multimedia[0] &&
story.multimedia[0].caption
? story.multimedia[0].caption
: null
}
link={story.url}
/>
);
})}
</ul>
</>
);
}
}
export default News;
Respuesta #1
Typescript no entiende el tipo de null
const [error, setError] = useState<string>(null);
Respuesta #2
En tu código, hay dos lugares donde el compilador de TS no conoce los tipos exactos de algunos valores, y te advierte que has intentado hacer cosas (posiblemente) inválidas con ellos.
Agregar | null tampoco ayuda, ya que eso no es la causa del problema, y (a menos que estés usando la bandera --strictNullChecks) null se puede asignar a cualquier tipo por defecto, por lo que no es necesario escribirlo.
Los puntos problemáticos:
error.messageAunque compruebes la veracidad de
error, el compilador no puede deducir que no puede sernull. Y, por supuesto, acceder a una propiedad ennullsería un error, de ahí la advertencia.Para resolver este, puedes usar el operador de aserción de no nulo:
error!.messagestoriesComo este valor se establece dinámicamente a partir de una solicitud de red, TS no sabrá su tipo (no puede inferirlo a partir del valor predeterminado, ya que es
null). Eso hace que seaany, y las operaciones sobre él también son desconocidas para el compilador (por ejemplo, no sabe que.map()esArray#map(), por lo que tampoco conoce los tipos de los argumentos que pasará a su función de devolución de llamada).Para solucionar esto, tendrás que definir el tipo exacto (una interfaz) de
stories(y sus propiedades, de forma recursiva). Luego, pasa esa interfaz auseState, para definir el tipo de la variable de estado.Sin embargo, como
storiestambién esnulla veces, después de definir correctamente su tipo, tendrás que usar la afirmación de no nulo antes de acceder a la propiedad, por la misma razón mencionada arriba.
Para resumir, tu código debería verse así:
import React, { FC, ReactElement, useEffect, useState } from "react";
import Story from "./Story";
export interface NewsProps{
//Props of the component go here
//Don't confuse them with state or other variables
}
interface Result {
results: ResultsEntity[];
}
interface ResultsEntity {
section: string;
title: string;
abstract: string;
url: string;
multimedia?: MultimediaEntity[];
}
interface MultimediaEntity {
url: string;
caption: string;
}
const News:FC<NewsProps> = ():ReactElement => {
const [error, setError] = useState<Error | null>(null);
const [stories, setStory] = useState<Result | null>(null);
useEffect(() => {
const getCurrentPage = () => {
const url = new URL(window.location.href);
const page = url.pathname.split("/").pop();
return page ? page : "home";
};
const section = getCurrentPage();
fetch(
`https://api.nytimes.com/svc/topstories/v2/${section}.json?api-key=4fzCTy6buRI5xtOkZzqo4FfEkzUVAJdr`
)
.then((res) => res.json())
.then((data) => {
setTimeout(() => setStory(data), 1500);
})
.catch((error) => {
console.log("Error", error);
setError(error);
});
}, []);
if (error) {
// v-- Non-null assertion (it can't be null, you've checked beforehand)
return <div>Error: {error!.message}</div>;
} else
if (!stories) {
return <div>Loading...</div>
} else {
return (
<>
<ul className="stories">
// v-- Non-null assertion (it can't be null, you've checked beforehand)
{stories!.results.map((story, idx) => {
return (
<Story
key={idx}
title={story.title}
abstract={story.abstract}
img={
// vv-- It's enough to do this (optional chaining, new JS feature). Much more readable. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
story.multimedia?.[0].url ?? null
// ^^^^^^^^^^^ ^^^^-- Fallback value
// +++++++++++-- Thing that may be null or undefined
}
alt={
story.multimedia?.[0].caption ?? null //Same as above
}
link={story.url}
/>
);
})}
</ul>
</>
);
}
}
export default News;
Véalo en vivo en el TS Playground (no produce errores)
