feners
feners

Reputation: 675

Problems saving data passed through Redux when page refreshes or changes

I'm trying to save a user's item selection whenever this item selection is added to a cart. I use Redux to pass the item data whenever the user presses add to cart on a particular item. In my Cart component I can view the the item selection data of the last item the was added to cart. This user selection item data looks like Object {price: 25, item: "Hoodie", size: "medium"}. I want to be able to store each selection that is added to the cart in my Cart component. This is Cart:

import React, { Component } from 'react';
import {addCart} from './Shop'; 
import { connect } from 'react-redux';

export class Cart extends Component {
    constructor(props) {
        super(props);
        this.state = {items: this.props.cart,cart: [],total: 0};
    }

    itemBucket(item) {
        this.state.cart.push(this.state.items);
        this.countTotal();
    }

    countTotal() {
        var total = 0;
        console.log(this.state.cart);
        this.state.cart.forEach(function(item, index){
            total = total + item.price;
            console.log (total);
        })
    }

    componentDidMount () {
        window.scrollTo(0, 0);
        this.itemBucket();
    }

    render() {
        return(
            <div className= "Webcart" id="Webcart">
                <addCart cartItem={this.props.cart} />
            </div>
        );
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        onCartAdd: (cart) => {
            dispatch(addCart(cart));
        },
    }
}

function mapStateToProps(state) {
  return { cart: state.cart };
}

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

I've set up itemBucket() as a function to add each item selection to a cart array found in the state. However this does not work and only the last item added to the cart is passed. This may have to do with changing how my Redux Store is working, but I don't really know how to apply this. This is my Redux Store:

import { createStore, applyMiddleware } from 'redux';
import  reducer  from './reducers';
import thunkMiddleware from 'redux-thunk';
import {createLogger} from 'redux-logger';


const store = createStore(
  reducer,
  applyMiddleware(
    createLogger(),
    thunkMiddleware
  )
);
export default store; 

How can I save each item that is passed to Cart even when the page is refreshed or changed?

EDIT

Here is my reducer component:

import {ADD_CART} from './actions';

export default Reducer;

var initialState = {
  cart:{},
  data: [],
  url: "/api/comments",
  pollInterval: 2000
};

function Reducer(state = initialState, action){
    switch(action.type){
        case ADD_CART:
            return {
                ...state,
                cart: action.payload
            }

            default:
                return state 
    };
}

Upvotes: 5

Views: 7901

Answers (3)

meteorBuzz
meteorBuzz

Reputation: 3200

Currently, whats happening in your app is that every-time your page refreshes, the redux store is initialised and uses the default values provided by the reducers.

You can over-ride these default values by providing an object as the second argument into createStore.

const store = createStore(
    reducer, // default values provided by reducers
    {key: "value"}, // overwrites matching key/val pairs, think Object.assign with the first reducer argument
    applyMiddleware(createLogger(), thunkMiddleware)
)

This example uses the browser's localStorage to store and retrieve data.

The localStorage.js file uses the redux state as the data to store in localStorage.

localStorage.js

export const loadState = () => {
    try {
        let serializedState = localStorage.getItem('state')

        if (serializedState === null) {
            return undefined
        }
        let storageState = JSON.parse(serializedState)

        return storageState
    } catch (err) {
        return undefined
    }
}

export const saveState = (state) => {
    try {
        const serializedState = JSON.stringify(state)
        // saves state to localStorage
        localStorage.setItem('state', serializedState)
    } catch (err) {
        console.log('error and unable to save state', err)
    }
}

Now you can configure the redux store so when it initialises, the 'state' item in localStorage is retrieved and will over-ride the default reducer values.

The saveState function is what will persist your redux state. It achieves this by listening to changes within your redux store using store.subscribe(). When changes takes place, saveState will be called.

Install lodash to enable throttling otherwise saveState will be called too many times.

configureStore.js

import { createStore, applyMiddleware } from 'redux'
import  reducer  from './reducers';
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import { loadState, saveState } from './localStorage'
import throttle from 'lodash/throttle'

let middlewares = [createLogger(), thunkMiddleware]

const configureStore = () => {
    const localStorageState = loadState()

    const store = createStore(
        reducer,
        localStorageState,
        applyMiddleware(...middlewares)
    )

    // everytime the state changes, it will be saved to 
    store.subscribe(throttle(() => {
        saveState(store.getState())
    }, 1000))

    return store
}
export default configureStore

Now create your store in the following way.

index.js

import configureStore from './configureStore'

const store = configureStore()

This implementation demonstrates how to interact directly with localStorage and have taken this idea from Dan. You can later optimise this storage and retrieval process. Currently, anytime a change occurs in store, the whole redux state is written into localStorage.

Once you're nearer to establishing a data structure for your redux store, you can slowly break out the state trees and set them as individual items within localStorage. (One possible solution)

You then subscribe/listen to specific state trees instead of the whole store, and save them when changes occur.

store.getState().some.data.set instead of store.getState()

Also, check out npm, some people have created some cool ways to address this issue.

Upvotes: 3

Shishir Arora
Shishir Arora

Reputation: 5923

I've set up itemBucket() as a function to add each item selection to a cart array found in the state. However this does not work and only the last item added to the cart is passed.

use

 constructor(props) {
    super(props);
    this.state = {cart: this.props.cart,total: 0};
}
 itemBucket(item) {
            this.setState({cart : [...this.state.cart, item]});
        }
    componentDidUpdate(){
      this.countTotal();
    }

countTotal will show old cart if put in itemBucket as setState is not synchronous. you can put however put that in componentDidUpdate.

For between refreshes, either store cart on server by using a service call to post the count, or use localStorage/sessionStorage/indexedDb to keep it in client. And in componentWillMount, get this from above location and hydrate your redux store with that, on the client.

Upvotes: 1

Tom Van Rompaey
Tom Van Rompaey

Reputation: 3586

My recommendation is to use redux-persist to save the cart state into localStorage. It will be much easier compared to writing your own implementation and has an active community (so if you encounter any issues/bugs, you probably won't be the only one).

Redux Store

import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import reducer from './reducers';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';

const store = createStore(
  reducer,
  undefined,
  compose(
    applyMiddleware(createLogger(), thunkMiddleware),
    autoRehydrate()
  )
);

persistStore(store, { whitelist: ['cart'] });

export default store; 

Reducer

import { ADD_CART } from './actions';
import { REHYDRATE } from 'redux-persist/constants';

export default Reducer;

var initialState = {
  cart:{},
  data: [],
  url: "/api/comments",
  pollInterval: 2000
};

function Reducer(state = initialState, action){
  switch(action.type){
    case REHYDRATE:
      if (action.payload && action.payload.cart) {
        return { ...state, ...action.payload.cart };
      }
      return state;

    case ADD_CART:
      return {
        ...state,
        cart: action.payload
      }

      default:
        return state 
  };
}

See full documentation here: https://github.com/rt2zz/redux-persist

Upvotes: 4

Related Questions