staxwell staxwell - 9 days ago 6
React JSX Question

React and localStorage getting NaN

I've never really used localStorage before but it seemed easy enough. Alas, I'm running into some weird issues using it with React.

I'm trying to save the number of clicks for an item in localStorage so when I navigate around the app it saves the data.

I view the localStorage in the console and

setItem
works on click and increments by one like expected but a couple things are happening


  1. On the first click the number of clicks is disappearing from the DOM.

  2. After the first click, the value becomes
    NaN

  3. When I navigate to another page and use the browser navigation to return to that page. The information is gone. I'm guessing this has something to do w/ a React hook I'm not using properly but I could be wrong.



Here's the code.

import React, { Component } from 'react';
import {IndexLink} from 'react-router';
/* eslint-disable */
import Item from '../styles/LinkItem.css';

class LinkItem extends Component {
constructor(props) {
super(props);
this.state = {
clicks: 0,
text: this.props.link,
newText: null
};
}
getClicksCount() {
const localStorageRef = localStorage.getItem('link-' + this.props.link);

if(localStorageRef) {
this.setState({
clicks: JSON.parse(localStorageRef) + 1
})
}
}
componentWillMount() {
this.getClicksCount()
}
editLink() {
const newText = prompt('Update your link');
this.setState({
newText: newText
}, function(){
this.props.data.updateLink(this.props.index, this.state.newText);
})
}
handleClick(e) {
e.preventDefault();
const clicks = this.state.clicks;
this.getClicksCount();
}
render() {
return (
<tr>
<td>
<IndexLink onClick={this.handleClick.bind(this)} to={{pathname: 'landing/' + this.props.link}}>{this.state.newText != null ? this.state.newText : this.props.link}</IndexLink>
</td>
<td>{this.state.clicks}</td>
<td><button className="btn btn-default" onClick={this.editLink.bind(this)}>Edit</button></td>
<td><button className="btn btn-danger" onClick={this.props.data.deleteLink.bind(null, this.props.index)}>Delete</button></td>
</tr>
);
}
}

export default LinkItem;

Answer

The problem is here:

this.setState({
  clicks: JSON.stringify(localStorage.setItem('link-' + this.props.link, clicks + 1))
});

localStorage.setItem returns undefined. So you break your state and that's the reason why you have an empty click number.

Rename method componentWillMount into getClicksCount, and call it in the new componentWillMount and in the end of method handleClick. So it will be simpler methods with single responsibilities: handleClick just updates the localStorage, getClicksCount just updates the state.


UPD: I wrote some methods and comments. I didn't test it, but I think that's gonna work. Feel free to ask questions.

import React, { Component } from 'react';
import { IndexLink } from 'react-router';
/* eslint-disable */
import Item from '../styles/LinkItem.css';

class LinkItem extends Component {
  constructor(props) {
    super(props);
    this.state = {
      clicks: 0,
      text: this.props.link,
      newText: null
    };
  }
  // new methods:
  getStoreKey() {
    return 'link-' + this.props.link;
  }
  getClicksCount() {
    const key = this.getStoreKey();
    const value = localStorage.getItem(key);
    return JSON.parse(value);
  }
  setClicksCount(newValue) {
    const key = this.getStoreKey();
    localStorage.setItem(key, newValue);
  }
  updateClicksCountState(val) {
    this.setState({
      clicks: val,
    });
  }
  //
  componentWillMount() {
    this.updateClicksCountState(this.getClicksCount()); // that should update state with value from localStorage
  }
  editLink() {
    const newText = prompt('Update your link');
    this.setState({
      newText: newText
    }, function(){
      this.props.data.updateLink(this.props.index, this.state.newText);
    })
  }
  handleClick(e) {
    e.preventDefault();
    const oldValue = this.getClicksCount();
    const newValue = oldValue + 1;
    this.setClicksCount(newValue);         // that will update value in localStorage
    this.updateClicksCountState(newValue); // that will update state -> render component
  }
  render() {
      return (
        <tr>
          <td>
              <IndexLink onClick={this.handleClick.bind(this)} to={{pathname: 'landing/' + this.props.link}}>{this.state.newText != null ? this.state.newText : this.props.link}</IndexLink>
          </td>
          <td>{this.state.clicks}</td>
          <td><button className="btn btn-default" onClick={this.editLink.bind(this)}>Edit</button></td>
          <td><button className="btn btn-danger" onClick={this.props.data.deleteLink.bind(null, this.props.index)}>Delete</button></td>
        </tr>
    );
  }
}

export default LinkItem;