#JSON.parse 'string | null' is not assignable to 'string'

16 messages Β· Page 1 of 1 (latest)

dapper acorn
#

I'm trying to write a bit of state which checks if the value in localStorage is a string, if it's null, it should return empty, however the value is a string it should be parsed.

I understand that JSON.parse requires a string, and so I'm not sure what I'm missing when it comes to this narrowing function.

  // const [theme, setTheme] =          useState(JSON.parse(localStorage.getItem('theme')) || '');
  const [theme, setTheme] = useState(() => {
      if (localStorage.getItem('theme') !== null) {
        return JSON.parse(localStorage.getItem('theme'))
      }
      else {
        return '';
      }
    }
  );

I also tried this...

    if (typeof localStorage.getItem('theme') === 'string') {
      return JSON.parse(localStorage.getItem('theme'))
    }

The error I'm getting is this:

Argument of type 'string | null' is not assignable to parameter of type 'string'.
  Type 'null' is not assignable to type 'string'.ts(2345)

Thanks for your help 😎

#

JSON.parse 'string | null' is not assignable to 'string'

hushed sapphire
#

@dapper acorn You need to move the thing into a variable before doing the check

#
const themeData = localStorage.getItem('theme');
if(themeData !== null) {
    return JSON.parse(themeData);
}
#

(You could also do typeof themeData === "string" instead of the null check, I just like the null check version)

#

The issue is that TS doesn't know that localStorage.getItem("theme") returns the same thing in both cases so it doesn't narrow if you call it twice.

dapper acorn
#

Thanks so much for this.

If you don't mind humouring me, could you help me understand why TS doesn't know that localStorage.getItem("theme") will return the same thing both times?

If there's a solid reason for this, I'll be much more likely to remember.

Thanks again πŸ™ŒπŸ™

hushed sapphire
# dapper acorn Thanks so much for this. If you don't mind humouring me, could you help me unde...

The only thing TS knows about the function is its signature, and the signature is:

(key: string) => string | null

There's just nothing in that that indicates that it returns the same thing every time, another function that has the same signature is:

function myGetItem = (key: string) {
   if(Math.random() > .5) return key;
   return null;
}

and that one doesn't return the same thing every time.

#

In theory maybe TS could have some mechanism for functions to indicate that they're pure functions (return the same outputs for a given input) and it could more aggressively type-guard pure functions, but it doesn't currently. (And even then, getItem isn't pure - this is only true if TS can know for sure that the code is synchronous and there aren't any calls to setItem between the getItem calls)

dapper acorn
#

I was just about to write this -> I understand the second example you gave. There it really is a matter of chance, but I'm not sure how this code is random when it's syncronously predictable..

Your second message really makes sense. I guess it would be a nightmare for the TS team to include synchronous feature type logic to functions that aren't guaranteed to have the same output as input...

hushed sapphire
#

Yeah, the key is that TS really doesn't know anything about a function except what its signature includes.

The Rust language functions similarly, and Steve Labnik has called this "the Golden Rule of Rust" https://steveklabnik.com/writing/rusts-golden-rule.

dapper acorn
#

I'm going to be honest now, I struggle with TS signatures. Could you explain to me how and why the functions signature is (key: string) => string | null?

hushed sapphire
#

Hmm, which part is confusing? That says that the function takes a string as input and returns either a string or null as output.

dapper acorn
#

When I look at the original faulty function that has this signature...

  const [theme, setTheme] = useState(() => {
      if (localStorage.getItem('theme') !== null) {
        return JSON.parse(localStorage.getItem('theme'))
      }
      else {
        return '';
      }
    }
  );

The initial state is a function, but the function doesn't take any parameters which I think is why I'm getting confused.

That being said, if the value of the theme is a string... You know what it could just be my understanding of useState needs refining..

hushed sapphire
#

Yeah, the signature of the function that you're providing to useState is () => string.

#

The signature of useState itself is a bit more complicated: it's generic and overloaded.