12

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?

Leon Gaban
  • 36,509
  • 115
  • 332
  • 529

5 Answers5

40

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.

OZZIE
  • 6,609
  • 7
  • 55
  • 59
  • 4
    Thank you for such a simple fix! – Aqeeb Imtiaz Harun Sep 22 '21 at 19:52
  • 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. – ruttergod Jun 16 '22 at 18:46
  • 2
    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);` – Joseph Aug 10 '22 at 20:43
14

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);
}
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • 6
    Thanks! 10 mins.... also just found out that this also works: `JSON.parse(localStorage.getItem('moonPortfolio') || '{}');` – Leon Gaban Feb 15 '19 at 18:42
  • 1
    @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. – p.s.w.g Feb 15 '19 at 18:45
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.

Ryan Cavanaugh
  • 209,514
  • 56
  • 272
  • 235
  • `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`? – Sandy Gifford Nov 10 '21 at 22:44
  • 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. – Ryan Cavanaugh Nov 10 '21 at 23:35
  • Shouldn't that behavior (implicit or not) be covered by the typings? – Sandy Gifford Nov 11 '21 at 15:09
  • No? You wouldn't want parseInt(true) to not have a type error. See https://stackoverflow.com/questions/41750390/what-does-all-legal-javascript-is-legal-typescript-mean – Ryan Cavanaugh Nov 11 '21 at 18:41
  • 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. – Sandy Gifford Nov 11 '21 at 19:35
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...
    //}
  }
DoomGoober
  • 1,533
  • 14
  • 20
0

also in react:

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

or

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