Typescript/React: "Object is possibly 'null'"

Viewed 7068

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.

How do I fix the two errors in the Parent below?


  • 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'.

My Interfaces


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;
}

Parent Component


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;

Child


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;
2 Answers

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}

For both cases, you can use the "?" operator ( Optional Chaining ).

 <div>Error: {error?.message}</div>;

 // This is similar to ( check if error is present then access the property on it )
 <div>Error: {error && error.message}</div>;
<ul className="stories">
      {stories.results?.map((story, idx) => {
        return (
          <Story
            key={idx}
            title={story.title}
            abstract={story.abstract}
            img={story.multimedia[0].url}
            alt={story.multimedia[0].caption}
            link={story.url}
          />
        );
      })}
    </ul>

NOTE - In the second case I am putting "?" in front of results, the reason is in your interface "NewsProps" results property can be undefined or null.

Ref: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining

-- After edit --

  • Your alt issue is coming because on the Story component you are expecting the type of alt as "string" or "undefined" ( you added ? it makes a property option hence undefined ). But you are passing "null" at the end of all the checking from the parent hence it is giving you a type error, one way to fix it is by using empty strings "" instead of null.
interface StoryProps {
  title?: String;
  img?: string;
  alt?: string; // -> This is the one string | undefined
  abstract?: string;
  link?: string;
}

 <Story
   key={idx}
   title={story.title}
   abstract={story.abstract}
   img={story.multimedia[0].url}
   alt={ 
    story &&
    story.multimedia && 
    story.multimedia[0] &&
    story.multimedia[0].caption 
    ? story.multimedia[0].caption 
    : null // -> here use "" instead of null
   }
    link={story.url}
 />
Related