Home > Software engineering >  TypeScript - Not respecting an undefined check in React
TypeScript - Not respecting an undefined check in React

Time:01-26

I am showing this IconButton element conditionally,

{value.content &&
   <IconButton aria-label="copy" onClick={() => copyContent(value.content)}>
     <ContentCopy />
   </IconButton>
}

However I keep getting the typescript warning Argument of type 'string | undefined' is not assignable to parameter of type 'string'. for parameter value.content in copyContent(value.content) . I thought value.content would make the check if its undefined, and if not, not show the element, and no need to worry about the onClick.

Any ideas what I am I missing here? I tried adding value.content && value.content !== undefined, but still get the error.

Thanks.

CodePudding user response:

You narrowed the type of something in a scope, and then created a function from that to be executed later. And the problem is that, because the function is executed later, you can't be certain that the narrowed you did still applies.

For example:

const value: { content: string | null } = { content: "abc" }

if (value.content) {
  setTimeout(() => {
    console.log(value.content.toUpperCase()) // type error
  }, 1000)
}

value.content = null

This code would crash at runtime. You check value.content, and all seems good. But after you check it the value gets cleared. Then one second later the function runs and value.content.toUpperCase() crashes because value.content is now null.

Maybe that object will change, maybe it won't. But the point is that the Typescript cannot guarantee that it will not change.


You could check it again.

const value: { content: string | null } = { content: "abc" }

if (value.content) {
  setTimeout(() => {
    value.content && console.log(value.content.toUpperCase()) // fine
  }, 1000)
}

value.content = null

Now you've protected yourself against a possible change from some other code.


But what I recommend is to reference content as a local const which can't be reassigned because it's a const.

const value: { content: string | null } = { content: "abc" }

const content = value.content
if (content) {
  setTimeout(() => {
    console.log(content.toUpperCase()) // fine
  }, 1000)
}

value.content = null // does not change the value in `const content` 

And now a change to the value object doesn't matter, because you've pulled the content from it, and if it's changed later then it won't affect this function at all.

See Playground


Or more specific to your case, something like:

function MyComp({ value }: { value: { content: string | null } }) {
  const content = value.content

  return <>{content &&
    <div onClick={() => copyContent(content)}>
      clickMe
    </div>
  }</>
}

See Playground

  • Related