Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 
    };
}
like image 791
feners Avatar asked Jul 18 '17 01:07

feners


2 Answers

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

like image 182
Tom Van Rompaey Avatar answered Oct 08 '22 04:10

Tom Van Rompaey


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.

like image 22
meteorBuzz Avatar answered Oct 08 '22 06:10

meteorBuzz