Problems persisting data passed through Redux on page refresh or change
I am trying to keep the selection of a user's item when that item is added to the cart. I am using Redux to pass item data when user clicks on cart on a specific item. In my component, Cart
I can view the item selection data of the last item that was added to the cart. The data for this user pick looks like Object {price: 25, item: "Hoodie", size: "medium"}
. I want to be able to store every selection added to the cart in my component Cart
. 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 configured itemBucket()
as a function to add each item to the basket array found in state
. However, this doesn't work and only the last item added to the cart gets passed. This may be due to a change in how my Redux store works, but I don't 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 do I keep every element that is passed to Cart
, even if 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
};
}
source to share
My recommendation is to use redux-persist
to store the state of the cart in localStorage. It will be much easier than writing your own implementation and having an active community (so if you run into any issues / bugs you probably won't be the only ones).
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 the full documentation here: https://github.com/rt2zz/redux-persist
source to share
Currently, what is happening in your application is that every time the page is refreshed, the redux store is initialized and uses the default values โโprovided by the reducers.
You can overload these defaults by providing an object as the second argument to 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 localStorage browser to store and retrieve data.
The file localStorage.js
uses redux state as data to be stored 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)
}
}
You can now configure the reducer store, so when it is initialized, the "state" item in localStorage is retrieved and will exceed the default reducer values.
A function saveState
is what will persist in the reduction state. This is achieved by listening for changes in your redux store with store.subscribe()
. When changes occur, is called saveState
.
Set 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 as follows.
index.js
import configureStore from './configureStore'
const store = configureStore()
This implementation demonstrates how to interact directly with localStorage and took that idea from Dan. You can optimize this storage and retrieval process later. Currently, anytime a store change occurs, the entire reduction state is written to localStorage.
Once you get close to creating the data structure for your redux store, you can slowly dump the state trees and set them as separate items in localStorage. (One possible solution)
Then you subscribe / listen to specific state trees instead of the whole store and save them when changes occur.
store.getState().some.data.set
instead store.getState()
Also, check npm, some people have created some cool ways to solve this problem.
source to share
I've set itemBucket () as a function to add each item to the cart array found in the state. However, this doesn't work and only the last item added to the cart gets 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 the old cart if put in itemBucket since setState is not synchronous. you can put it in componentDidUpdate.
In between updates, either store the cart on the server using a service call to post the invoice, or use localStorage / sessionStorage / indexedDb to store it on the client. And in componentWillMount, get this from the upstream location and wet your redux store with this, on the client.
source to share