Roly Poly
Roly Poly

Reputation: 559

Redux, localStorage, and React: Getting an error when refreshing my page

My goal is to have my React state persist through a refresh. I've seen it done in a tutorial (just click around in there if you wanna see) using the browser's localStorage. I want to use that solution because it looks simple to me relative to busting out Redux Persist.

The expected result is a page that refreshes and reloads as it was before. Instead, I get an error. I suspect the error happens because the Redux state unloads(?), then my code tries to point const threadTitle at the state in this.props.posts[number] which isn't there of course because refreshing just unloaded it.

The error I get reads as follows:

TypeError: Cannot read property 'title' of undefined
fullThread.render
C:/Users/Roland/React_apps/reddit-knockoff/src/FullThread/FullThread.js:30
  27 | 
  28 |  // deliberate exclusion of 2nd parameter in .slice() to make it go til the end
  29 |  const number = this.props.location.pathname.slice(1);
> 30 |  const threadTitle = this.props.posts[number].title;
     | ^  31 |  let flair = null;
  32 |  const upVotes = this.props.posts[number].upvotes;
  33 |  const author = this.props.posts[number].author;

So far I've tried watching tutorial videos and raiding Google's StackOverflow results for hints about how to use localStorage to solve the issue. I've got some code up and running, but it isn't enough. Have a look:

FullThread.js, where the error message is pointing to.

import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";

class fullThread extends Component {
    constructor(props) {
        super(props);
        const stateFromStorage = localStorage.getItem("state");
        if (this.props.posts === null) {
            this.props.returnPostsToState(stateFromStorage);
        }
    }

    componentDidMount() {

        localStorage.setItem("state", this.props.posts);
    }

    render() {

        // deliberate exclusion of 2nd parameter in .slice() to make it go til the end
        const number = this.props.location.pathname.slice(1);
        const threadTitle = this.props.posts[number].title;
        const upVotes = this.props.posts[number].upvotes;
        const author = this.props.posts[number].author;
        const age = this.props.posts[number].age;
        const postText = this.props.posts[number].postText;


        return (
            <div>
                <div className="MainBody">
                    <h4>Thread titled: {threadTitle}</h4>
                    <p>Removed content. The constants threadTitle, upVotes, author, age and postText go here</p>

                </div>
            </div>
        );
    }
}

const mapDispatchToProps = dispatch => {
    return {
        returnPostsToState: stateFromStorage =>
            dispatch({ type: "RETURN", payload: stateFromStorage })
    };
};

const mapStateToProps = state => {
    return {
        posts: state.posts
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(fullThread);

posts.js, which contains my Reducer

const initialPosts = {
    posts: [
        {
            id: 0,
            upvotes: 831,
            flair: null,
            title: "New? READ ME FIRST!",
            author: "michael0x2a",
            age: "2 years",
            comments: 15,
            postText:
                "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
        }
    ],
    loggedIn: false
};

const reducer = (state = initialPosts, action) => {
    if (action.type === "ADD_POST") {
        console.log("HEY: ", action.newPost);
        console.log(state.posts.concat(action.newPost.post.tempThread));
        return {
            ...state,
            // receives newPost data from app.js
            posts: state.posts.concat(action.newPost.post.tempThread)
        };
    }
    if (action.type === "RETURN") {
        console.log("RETURNED! ", action.payload);
        console.log("RETURNED: ", state.posts);
        return {
            ...state,
            posts: state.posts.concat(action.payload)
        };
    }

    return state;
};

export default reducer;

I would post app.js but I don't think it's relevant.

My contention with my code and the error is that, well, I think my code should be working! First I call localStorage.setItem("state", this.props.posts); in my componentDidMount() method to set up my localStorage with the content of my posts state variable. Then when the page reloads, my constructor method -- which fires first, before any other code in the component lifecycle!!! -- calls this.props.returnPostsToState(stateFromStorage);, thereby resetting the global Redux state with the contents of "state From Storage", which IS my original posts content!

What am I doing wrong?

I don't know how to implement it, but, I think there is some working solution where I go, "if (stateHasReloaded) { const threadTitle = this.props.posts[number].title; }" I just don't know exactly what it looks like yet.

Thanks in advance for any help

Upvotes: 0

Views: 640

Answers (1)

Kaushal Madani
Kaushal Madani

Reputation: 78

I think that when you are getting data from localstorage,it will be string so after that your component will not be able to parse it.Try to parse data when you are getting from local storage.

 if (this.props.posts === null) {
      this.props.returnPostsToState(JSON.parse(stateFromStorage));
 }

And also when you are storing to localstorage,stringify you data as :

componentDidMount() {

    localStorage.setItem("state", JSON.stringify(this.props.posts));
}

And for the safer side you can always check that data is there or not because you are getting number from url so anyone change it to the number that posts is not having:

const threadTitle = this.props.posts[number]?this.props.posts[number].title:'';

Let me if it is working or not else will see.

Upvotes: 1

Related Questions