creativeAxe
creativeAxe

Reputation: 119

localStorage gets reset after button click ReactJS

I'm currently experiencing an issue where local storage is reset after trying to add an item that belongs to a different page(ex shown below) to the local storage. The local storage functionality is being used for a shopping cart feature.

It all works well when I try adding items from the same category, but once I switch the the category the local storage is reset.

A weird behavior that I also noticed is that for the first item that I try to add to the cart, I have to double click it for it to register in the local storage.

I've set the program so that only the shopping cart page needs to access the local storage.

Adding product from "Wearables category" enter image description here

Going back and into the Items in the "Computers" section. Ignore sidebar enter image description here

Adding item from wearable section and local storage is cleared. enter image description here

Code: App.js

class App extends Component {
  userData;
  constructor(props) {
    super(props);

    this.state = {
      cart: [],
    };

    this.handleAddToCart = this.handleAddToCart.bind(this);
  }

  handleAddToCart = (productId, prodName, description, price) => {
    console.log(" Handle Add to Cart Called ", productId);
    console.log("->cart state: ", this.state.cart);
    const holder = {
      productId,
      quantity: 1,
      prodName,
      description,
      price,
    };

    const idx = this.indexOfProduct(productId);

    if (idx == -1) {
      // Product does not exist in cart
      this.setState(
        {
          cart: [...this.state.cart, holder],
        },
        () => {
          console.log("Updated Cart: ", this.state.cart);
        }
      );
    } else {
      let newArray = [...this.state.cart];
      newArray[idx] = {
        ...newArray[idx],
        quantity: newArray[idx].quantity + 1,
      };
      this.setState(
        {
          cart: newArray,
        },
        () => {
          console.log("Updated Cart: ", this.state.cart);
        }
      );
    }
    localStorage.setItem("cart", JSON.stringify(this.state.cart));
  };

  indexOfProduct(productId) {
    for (let index = 0; index < this.state.cart.length; index++) {
      if (this.state.cart[index].productId == productId) return index;
    }
    return -1;
  }

  render() {
    return (
      <div className="App">
        {/* <div className="container-fluid">
          <NavBarComponent />
        </div> */}
        <>
          <Router>
            <div className="container-fluid">
              <NavBarComponent />
            </div>

            <Switch>
              <Route exact path="/sidebar">
                <SideBarComponent />
              </Route>
              <Route exact path="/products/:category">
                <ProductGridComponent />
              </Route>
              <Route exact path="/cart">
                <ShoppingCartComponent />
              </Route>
              <Route exact path="/product/:id">
                {/*onAddToCart={this.handleAddToCart} */}
                <ProductViewComponent onAddToCart={this.handleAddToCart} />
              </Route>

              <Route exact path="/contact">
                <ContactUsComponent />
              </Route>

              <Route exact path="/about-us">
                <AboutUsComponent />
              </Route>
              <Route exact path="/">
                <HomeComponent />
              </Route>
            </Switch>
          </Router>
        </>
        <FooterComponent />
      </div>
    );
  }
}

export default App;

ShoppingCartComponent.jsx

class ShoppingCartComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      cart: [],
    };
    console.log("Hello Im the constructor");
  }
  static getDerivedStateFromProps(props, state) {
    console.log("Hello Im the dState Func");

    const sCart = localStorage.getItem("cart");
    const parsedCart = JSON.parse(sCart);

    if (sCart == null) {
      return { cart: [] };
    } else {
      console.log("cart String mount on shopping cart: ", sCart);
      console.log("cart Object at mount on shopping cart: ", parsedCart);
     

      return { cart: parsedCart };
      console.log("After appending", this.state.cart);
    }
  }


  render() {
    console.log("Shopping Cart Array at Render(): ", this.state.cart);
    return (
      <div className="container mt-5 p-3 rounded cart">
        <div className="row no-gutters">
          <div className="col-md-8">
            <div className="product-details mr-2">
              <div className="d-flex flex-row align-items-center">
                <i className="fa fa-arrow"></i>

                <button /* onClick={history.back} */>
                  <span className="ml-2">
                    <a style={{ color: "black" }}>Continue Shopping</a>
                  </span>
                </button>
              </div>
              <hr />
              <h6 className="mb-0">Shopping cart</h6>
              <div className="d-flex justify-content-between">
                <span>
                  You have {this.state.cart.length} items in your cart
                </span>
                <div className="d-flex flex-row align-items-center">
                  <span className="text-black-50">Sort by:</span>
                  <div className="price ml-2">
                    <span className="mr-1">price</span>
                    <i className="fa fa-angle-down"></i>
                  </div>
                </div>
              </div>

              {this.state.cart.map((product) => (
                <div className="d-flex justify-content-between align-items-center mt-3 p-2 items rounded">
                  <div className="d-flex flex-row">
                    <img
                      className="rounded"
                      src="https://i.imgur.com/QRwjbm5.jpg"
                      width="40"
                    />
                    <div className="ml-2">
                      <span className="font-weight-bold d-block">
                        {product.prodName}
                      </span>
                      <span className="spec">256GB, Navy Blue</span>
                    </div>
                  </div>

                  ...
         

ProductGridComponent.jsx //Where the products per categories are displayed. Sidebar is a separate component.

class ProductGridComponent extends Component {
  constructor(props) {
    super(props);

    const windowUrl = window.location.pathname.substring(1);
    console.log("window url: ", windowUrl);

    this.state = {
      category: windowUrl.substring(windowUrl.indexOf("/") + 1), //Please fix me, I am vulnerable to SQL Injection
      products: [],
    };
    console.log(this.state.category);

    this.handleShopButtonClick = this.handleShopButtonClick.bind(this);
  }
  componentDidMount() {
    ProductService.getProductsByCategory(this.state.category).then((res) => {
      this.setState({ products: res.data });
    });
  }
  handleShopButtonClick(productId) {
    this.props.history.push(`/product/${productId}`);
  }
  onAddClick() {}

  render() {
    return (
      <>
        {/* <div className="container-fluid page-body-wrapper"> */}
        <div className="wrapper">
          <SideBarComponent />
          <div className="row" style={{ marginLeft: "5px" }}>
            {this.state.products.map((product) => (
              <div className="col col-md-3" style={{ marginTop: "5px" }}>
                <div className="card">
                  <div className="d-flex justify-content-between align-items-center">
                    <div className="d-flex flex-row align-items-center time">
                      <i className=""></i>
                      <small className="ml-1">{product.vendorName}</small>
                    </div>
                  </div>
                  <div className="text-center">
                    <img src="https://i.imgur.com/TbtwkyW.jpg" width="250" />
                  </div>
                  <div className="text-center">
                    <h5>{product.prodName}</h5>
                    <span className="text-success">${product.price}</span>
                  </div>
                  <div>
                    <Link to={`/product/${product.id}`}>
                      <button
                        className="btn btn-outline-dark flex-shrink-0"
                        type="button"
                        style={{ marginLeft: "10px" }}
                      >
                        <i
                          className="bi-bag-fill me-1"
                          style={{ marginRight: "4px" }}
                        ></i>
                        Buy Now
                      </button>
                    </Link>
                    <Link to={`/product/${product.id}`}>
                      <button
                        className="btn btn-outline-dark flex-shrink-0"
                        type="button"
                        style={{ marginLeft: "10px" }}
                      >
                        <i className=""></i>
                        View
                      </button>
                    </Link>
                  </div>
                </div>
              </div>
            ))}
            {/* <img src="https://i.imgur.com/aTqSahW.jpg" width="250" /> */}
          </div>
        </div>
      </>
    );
  }
}

export default ProductGridComponent;

ProductViewComponent.jsx

class ProductViewComponent extends React.Component {
  constructor(props) {
    super(props);

    const windowUrl = window.location.pathname.substring(1);
    console.log("window url for product: ", windowUrl);

    this.state = {
      //id: this.props.match.params.id,
      id: windowUrl.substring(windowUrl.indexOf("/") + 1), //Please fix me, I am vulnerable to SQL Injection
      name: "",
      price: 0,
      vendor: "holder vendor",
      description: "",
    };
    console.log("ID: ", this.state.id);
  }

  componentDidMount() {
    ProductService.getProductById(this.state.id).then((res) => {
      let product = res.data;

      this.setState({
        name: product.prodName,
        price: product.price,
        vendor: product.vendorName,
        description: product.description,
      });
    });
  }

  render() {
    return (
...
  <button
                    className="btn btn-outline-dark flex-shrink-0"
                    type="button"
                    style={{ marginLeft: "10px" }}
                    onClick={() =>
                      this.props.onAddToCart(
                        this.state.id,
                        this.state.name,
                        this.state.description,
                        this.state.price
                      )
                    }
                  >
                    <i className="bi-cart-fill me-1"></i>
                    Add to cart
                  </button>
...

Upvotes: 1

Views: 1403

Answers (2)

creativeAxe
creativeAxe

Reputation: 119

The issue was a logical error. On reload, the state(cart) becomes empty. However, I never set the state to the items already on local storage before trying add the new items to the cart. The way I did the adding functionality involves taking the current state and appending the new items, and then setting the local storage afterwards. Meaning that if the state is empty then then trying to add a new item this way will simply result in only the new item being on the cart.

I added this piece to App.js to solve the error.

componentDidMount() {
    const sCart = localStorage.getItem("cart");
    const parsedCart = JSON.parse(sCart);

    if (sCart == null) {
      this.setState({ cart: [] });
    } else {
      console.log("cart String mount on shopping cart: ", sCart);
      console.log("cart Object at mount on shopping cart: ", parsedCart);
      this.setState(
        {
          cart: parsedCart,
        }
      );
    }
  }

Upvotes: 1

Willey Ohana
Willey Ohana

Reputation: 402

I was struggling with a similar problem earlier today. My localStorage was getting rewritten upon refresh even though localStorage is often used to carry data over between refreshes / browser closures.

The problem that I realized was that I was setting the localStorage to the state upon render, and my state was initialized to an empty value. It looks like you are only calling localStorage.setItem() once in your code, and it is setting it to the state. The problem is that when localStorage.setItem() is called, your state is still an empty array.

React's this.setState() method is an asynchronous method, meaning that it will be added to a stack to be run. It gives no promises on when it will start being run or when it will finish. It looks like you are calling this.setState() right before you call localStorage.setItem(), which means that it is not updating the state in time before you are changing the localStorage.

What I would suggest is putting the call to localStorage inside of the callback function that is the second parameter of this.setState(). This callback function is always run after the state has been set.

Your state setter will look like this:

      this.setState(
        {
          cart: [...this.state.cart, holder],
        },
        () => {
          console.log("Updated Cart: ", this.state.cart);
          localStorage.setItem("cart", JSON.stringify(this.state.cart));
        }
      );

Upvotes: 2

Related Questions