jeth318
jeth318

Reputation: 65

How to set React state from localStorage?

I'm having a hard time figuring out how to set a component state from localStorage using React and TS. After I check if the localStorage instance exists, it still says incompatible type since localStorage.getItem() still can return string or null.

If I change my interface and add string | null I can set the state from localStorage, however I'm no longer able to set the localStorage it self since localStorage.setItem() seem to only accept string and not string | null

Edit

I came up with this "solution" to the problem. I'm not sure if it's bad practice but it seems to work. If this can be written in a better way, please let me know.

componentWillMount() {
    let from = localStorage.getItem('from');
    let to = localStorage.getItem('to');
    let fromId = localStorage.getItem('fromId');
    let toId = localStorage.getItem('toId');

    if (typeof from === 'string' &&
        typeof to === 'string' &&
        typeof fromId === 'string' &&
        typeof toId === 'string') {

      this.setState({
        inputFrom: from,
        inputTo: to,
        fromId: fromId,
        toId: toId,
      });
    }
  }

The compilation error:

Argument of type '{ inputFrom: string | null; }' is not assignable to parameter of type 'Pick'. Types of property 'inputFrom' are incompatible. Type 'string | null' is not assignable to type 'string'. Type 'null' is not assignable to type 'string'.

My interface is implemented as following:

interface State {
  inputFrom: string,
  inputTo: string,
  fromId: string,
  toId: string,
  tripdata: {},
  loading: boolean,
  error: boolean,
}

My component

import * as React from 'react';
import Header from './components/Header';
import SearchForm from './components/search/SearchForm';
import API from './api/APIService';
import TripTable from './components/trips/TripTable';
import Loading from './components/atoms/Loading';
import Error from './components/atoms/Error';
import './App.css';
const api = new API;

interface State {
  inputFrom: string,
  inputTo: string,
  fromId: string,
  toId: string,
  tripdata: {},
  loading: boolean,
  error: boolean,
}

class App extends React.Component<any, State> {
  constructor() {
    super();
    this.state = {
      inputFrom: '',
      inputTo: '',
      fromId: '',
      toId: '',
      tripdata: {},
      loading: false,
      error: false,
    };
  }

  componentWillMount() {
    let from = localStorage.getItem('from');
    if (from !== null || from !== undefined) {
      this.setState({
        inputFrom: from,
      });
    }
  }

  handleSubmit = () => {
      if (this.state.fromId !== '' && this.state.toId !== '') {
        this.setState({error: false});
        this.setState({loading: true});
        api.GetAccessToken()
        .then((token: string) => {
          api.GetTripFromSearch(token, this.state.fromId, this.state.toId)
        .then((res) => {
          this.setState({
            tripdata: res,
            loading: false,
          });
        });
          localStorage.setItem('from', this.state.inputFrom);
          localStorage.setItem('to', this.state.inputTo);
          localStorage.setItem('fromId', this.state.fromId);
          localStorage.setItem('toId', this.state.toId);
      });
    } else {
      this.setState({error: true});
    }
  }

  render() {
    let triptable: JSX.Element | null;
    let loading: JSX.Element | null;
    let error: JSX.Element | null;

    if (this.state.tripdata !== null) {
      triptable = <TripTable tripdata={this.state.tripdata} />;
    } else {
      triptable = null;
    }
    if (this.state.loading) {
      loading = <Loading />;
      triptable = null;
    } else {
      loading = null;
    }
    if (this.state.error) {
      error =  <Error />;
    } else {
      error = null;
    }
    return (
        <div>
          <Header />  
          <SearchForm 
            resetInputId={this.resetInputId}
            handleInputFrom={this.handleInputFrom}
            handleInputTo={this.handleInputTo}
            handleSubmit={this.handleSubmit}
            error={error}
            handleSwap={this.handleSwap}
          />
          {loading}
          {triptable}
        </div>        
    );
  }
}


export default App;

Upvotes: 5

Views: 3491

Answers (1)

a.anev
a.anev

Reputation: 135

In my specific case I was trying to get a boolean out of local storage. Local storage doesn't save booleans. So if you give it 'true' for instance it will save it as a string. That's what the if is for. In practice you can make this look prettier but I used the else if here for clarity.

I placed the checkLocalStorage function outside of the component in this case. I don't see why it wouldn't work inside the component but this post told me to put it outside the component so that's what I did.

ps: If there is nothing in local storage you get a null. That's what I use to set the default value.

const checkLocalStorage = () => {
  const ls = localStorage.getItem('item')
  if (ls === null || ls === 'true') {
    return true
  } else if (ls === 'false') {
    return false
  }
}

export const MyComponent = () => {

  const [state, setState] = useState(checkLocalStorage())

// rest of your code here

Upvotes: 1

Related Questions