15

The error was happening here:

let moonPortfolio;
...
moonPortfolio = JSON.parse(localStorage.getItem('moonPortfolio'));

I found this answer which makes sense, however I'm still getting that error after this refactor:

As the error says, localStorage.getItem() can return either a string or null. JSON.parse() requires a string, so you should test the result of localStorage.getItem() before you try to use it.

if (portfolio.length === 0) {
  const storedPortfolio = localStorage.getItem('moonPortfolio');

  if (typeof storedPortfolio === 'string') {
    moonPortfolio = JSON.parse(localStorage.getItem('moonPortfolio'));
  }
  else {
    moonPortfolio = [];
  }

  if (moonPortfolio) {
    const savedPortfolio = Object.values(moonPortfolio);
    this.props.fetchAllAssets();
    // this.props.addCoins(savedPortfolio);
  }
}

enter image description here

I first set the results of localStorage moonPortfolio to a var, then check if the var is typeof string. Yet still getting the typescript error?

Any thoughts or direction here?

5 Answers 5

48

Simple fix:

JSON.parse(localStorage.getItem('moonPortfolio') || '{}');

Seems like TS does know about the inner workings of localStorage/sessionStorage actually. It returns null if you try to fetch a key that isn't set. null when treated as boolean is falsy so by adding OR the empty stringified json object will be used instead meaning that JSON.parse(x) will always be given a string meaning it's then type safe.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for such a simple fix!
Yes this is all I needed lol. I was getting very frustrated by this, unable to get a string | null into JSON.parse any other way.
This isn't really the best solution. Instead of JSON.parse returning null, now it returns {}. The better solution is something like: const item = localStorage.getItem('moonPortfolio'); if (item === null) return null; return JSON.parse(item);
15

The compiler doesn't know too much about the inner workings of localStorage.getItem and doesn't make the assumption that the return value will be the same from one call of getItem to the next. So it just tells you that it can't be certain that on the second call to getItem the result isn't null.

Try simply passing in the variable you've already created instead of reading from localStorage again:

if (typeof storedPortfolio === 'string') {
  moonPortfolio = JSON.parse(storedPortfolio);
}

2 Comments

Thanks! 10 mins.... also just found out that this also works: JSON.parse(localStorage.getItem('moonPortfolio') || '{}');
@Leon True and that option could be safer if in the single edge case of an empty string (JSON.parse('') throws a SyntaxError), but still I'd prefer explicit type checks and a try/catch for invalid strings.
2

TypeScript doesn't know that multiple invocations of localStorage.getItem with the same string literal will always return the same value (in fact, this isn't even true).

The second call to localStorage.getItem('moonPortfolio') may well return null - you should call JSON.parse(storedPortfolio) instead of calling getItem again.

5 Comments

null is a valid value for JSON.parse (does not raise an error, returns null) - shouldn't the type def for the first argument of JSON.parse be string | null?
No, because the very first step of JSON.parse is an implicit coercion of its argument to a string. JSON.parse(32) "succeeds" for the same reason but is not allowed either.
Shouldn't that behavior (implicit or not) be covered by the typings?
No? You wouldn't want parseInt(true) to not have a type error. See stackoverflow.com/questions/41750390/…
I'd say that returning NaN is different than returning a variable value - still, what you've written in that question seems to act as documentation and clearly outlines what TypeScript does and does not do.
0

The main thing to know is that localStorage.getItem() returns string | null. Knowing that we can re-write the code with a simpler pattern:

  const portfolio = []; //Just to make the code samples valid
  if (portfolio.length === 0) {
    let moonPortfolio = []; //Default value
    const storedText = localStorage.getItem('moonPortfolio');
    if (storedText !== null) { //We know it's a string then!
      moonPortfolio = JSON.parse(storedText);
    }
    //The if statement here is not needed as JSON.parse can only return 
    //object | array | null or throw. null is the only falsy value and that 
    //can only happen if storedText is null but we've already disallowed 
    //that.
    //if (moonPortfolio) {
      const savedPortfolio = Object.values(moonPortfolio);
      //Do whatever else is needed...
    //}
  }

Comments

0

also in react:

JSON.parse(localStorage.getItem("data") ?? '{}')

or

JSON.parse(localStorage.getItem("data") || '{}')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.