I'm changing a React component to Typescript. It calls an API. I believe my interfaces are set up correctly, but I'm not passing the types correctly.
return <div>Error: {error.message}</div>; // "Object is possibly 'null'"
{stories.results.map((story, idx) => { // "Object is possibly 'null' or 'undefined'"
alt={ // Type 'string | null' is not assignable to type 'string | undefined'. Type 'null' is not assignable to type 'string | undefined'.
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;
alt:string;
}
import React, { FC, ReactElement, useEffect, useState } from "react";
import Story from "./Story";
const News: FC<NewsProps> = ({results:ResultsEntity}):ReactElement => {
const [error, setError] = useState(null);
const [stories, setStory] = useState<NewsProps>();
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) => {
setError(error);
});
}, []);
if (error) {
return <div>Error: {error.message}</div>; // "Object is possibly 'null'"
} else
if (!stories) {
return <div>Loading...</div>
} else {
return (
<>
<ul className="stories">
{stories.results.map((story, idx) => { // "Object is possibly 'null' or 'undefined'"
return (
<Story
key={idx}
title={story.title}
abstract={story.abstract}
img={story.multimedia[0].url}
alt={ // Type 'string | null' is not assignable to type 'string | undefined'. Type 'null' is not assignable to type 'string | undefined'.
story &&
story.multimedia &&
story.multimedia[0] &&
story.multimedia[0].caption
? story.multimedia[0].caption
: null
}
link={story.url}
/>
);
})}
</ul>
</>
);
}
}
export default News;
import React, { FC, ReactElement } from "react";
interface StoryProps {
title?: String;
img?: string;
alt?: string;
abstract?: string;
link?: string;
}
const Story: FC<StoryProps> = ({title, img, alt, abstract, link}): ReactElement => {
return (
<div className="story">
<li className="story-title">{title}</li>
<span className="story-content">
<img className="story-img" src={img} alt={alt} />
<span>
<li className="story-body">{abstract}</li>
</span>
</span>
</div>
);
};
export default Story;
For error
, the issue is that when you create the state, you didn't explicitly give it a type, so typescript has to try to infer the type. It sees you passed in null, so it assumes the state is (and always will be) null
:
const [error, setError] = useState(null);
If you know something about what the error will be, you can give a type that represents that. For example:
const [error, setError] = useState<null | { message: string }>(null);
If you know nothing, then you may need to resort to any
:
const [error, setError] = useState<any>(null);
For stories
, you've defined the .results
property as being possibly null or undefined:
export interface NewsProps {
results?: (ResultsEntity)[] | null;
}
...but then you don't check for those values before using it. You'll need to write code to check for those cases. For example, if you just want to skip the map statement and render nothing you can do this:
{stories.results && stories.results.map((story, idx) => {
Or the same thing with optional chaining (requires typescript 3.7 or later):
{stories.results?.map(story, idx) => {
For the alt
prop, you're passing in null
, but that's not a legal value for alt
. Pass in undefined
instead.
alt={
story &&
story.multimedia &&
story.multimedia[0] &&
story.multimedia[0].caption
? story.multimedia[0].caption
: undefined
}
Or with optional chaining:
alt={story?.multimedia?.[0]?.caption}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With