I need to concat an array from my reducer after add to cart button is pressed.
I tried pushed, but it doesn't seem to work.
import { combineReducers } from 'redux';
import { DATA_AVAILABLE,
ADD_TO_CART,
GET_CART_DATA
} from "../actions/" //Import the actions types constant we defined in our actions
let dataState = { data: [], loading:true };
let cartState = { data: [] };
const dataReducer = (state = dataState, action) => {
switch (action.type) {
case DATA_AVAILABLE:
state = Object.assign({}, state, { data: action.data, loading:false });
return state;
default:
return state;
}
};
const cartReducer = (state = cartState, action) => {
switch (action.type) {
case ADD_TO_CART:
state = Object.assign({}, state, { data: [action.data]});
//console.log("state data => "+state.data);
return state;
default:
return state;
}
};
// Combine all the reducers
const rootReducer = combineReducers({
dataReducer,
cartReducer,
// ,[ANOTHER REDUCER], [ANOTHER REDUCER] ....
})
export default rootReducer;
During ADD_TO_CART event, the reducer is replacing all the data each time my add to cart button is clicked. Instead, I need to concat those items so I can show them into my cart list.
Seems like you probably want:
case ADD_TO_CART:
return Object.assign({}, state, {
data : state.data.concat(action.data)
});
If you have the Object Spread syntax available in your app setup (which is turned on by default if you're using Create-React-App), you can simplify that a bit to:
case ADD_TO_CART:
return {...state, data : state.data.concat(action.data) }
Related
I am implementing a shop cart using react-redux.
I got two reducers,
1.To fetch cart data from DB
2. To Carry out various cart operations.
My doubt is after achieving data from DB through the first reducer, how will I access that data through the 2nd reducer in order to carry out different cart operations ?
Reducer 1 - Fetch Data from DB
const initialState={
loading:false,
items:[],
error:false
}
const CartFetch=(state=initialState,action)=>{
switch(action.type){
case FETCHDATA : return {
...state,loading:true ,error:false
};
case FETCHSUCCESS: return {
...state,loading:false,
items:[...action.payload]
};
case FETCHERROR : return {
...state,loading:false,error:true
};
default: return state;
}
}
Fetch Actions
const fetch=()=>{
return {
type:FETCHDATA
}
}
const success=(user)=>{
return {
type:FETCHSUCCESS,
payload:user
}
}
const error=()=>{
return {
type:FETCHERROR
}
}
const fetchCartData=()=>{
const {id}=getCurrentUser();
return (dispatch)=>{
dispatch(fetch());
axios.get(`${api.userOperations}/cart/${id}`,{
headers:{'Authorization': getJwt()}
}).then(({data})=>{
dispatch(success(data));
}).catch(()=>{
dispatch(error())
})
}
}
Reducer 2 - Cart Operations
const CartHandle=(state= ..?.. ,action)=>{
switch(action.type){
case ADD_TO_CART :
return {
......
};
case INCREMENT_CART : return {
....
};
case DECREMENT_CART: return {
......
};
case REMOVE_FROM_CART : return {
.....
};
default: return state;
}
}
}
Here in Reducer 2 how will I access the pass the data which I fetched in Reducer 1 ? Or easy there any better way of implementing what I m trying to ?
Combine Reducers
const allReducer=combineReducers({
Cart:CartFetch,
CartOperations: CartHandle
});
Store
const countStore=createStore(allReducer,applyMiddleware(thunk));
<Provide store={store}>
...App.js...
</Provider>
Issue
It seems you don't quite fully understand what a reducer represents. Each reducer represents a specific "chunk" or slice of state. No two reducers function/operate on the same slice of state. In other words, two separate reducers equals two separate slices of state.
Solution
Since a reducer represents a specific slice of state it needs to handle all the actions that are associated with that slice. You just need to merge your second reducer into the first on so it fully manages the cart state.
const initialState = {
loading: false,
items: [],
error: false
};
const cartReducer = (state = initialState, action) => {
switch (action.type) {
case FETCHDATA:
return {
...state,
loading: true,
error: false
};
case FETCHSUCCESS:
return {
...state,
loading: false,
items: [...action.payload]
};
case FETCHERROR:
return {
...state,
loading: false,
error: true
};
case ADD_TO_CART:
return {
// ......
};
case INCREMENT_CART:
return {
// ....
};
case DECREMENT_CART:
return {
// ......
};
case REMOVE_FROM_CART:
return {
// .....
};
default:
return state;
}
};
Create your root reducer, each combined reducer represents a slice of state.
const allReducer = combineReducers({
// ... other state slice reducers
cart: cartReducer,
// ... other state slice reducers
});
If I fetch this array of restos with redux:
[{
res_id: Int,
res_name: String,
res_category: String,
res_category_id: Int,
city_id: Int
}]
My action looks something like this:
export const getrestos = () => {
const resData = await response.json();
dispatch({
type: GET_RESTOS,
payload: resData
});
};
};
export const setFilters = filterSettings => {
console.log(filterSettings);
return { type: SET_FILTERS, filters: filterSettings };
};
And this is my reducer:
import { GET_RESTOS, SET_FILTERS } from '../actions/restos';
const initialState = {
restoList: [],
filteredRestos: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_RESTOS:
return {
restoList: action.payload
}
case SET_FILTERS:
const appliedFilters = action.filters;
const updatedFilteredRestos = state.restoList.filter(resto => {
if (appliedFilters.cityID || resto.city_id) {
resto => resto.city_id.indexOf(cityID) >= 0
return { ...state, filteredRestos: updatedFilteredRestos };
}
});
return { ...state, filteredRestos: updatedFilteredRestos };
default:
return state;
}
};
I have touchable categorys in a page, and when i touch one i want to fetch the corresponding restos for that category and show them in a flatlist. Apart from that i want to have a search bar that when I type I want to show restos by res_name and/or by res_category.
Ive tried to create selectors, but I dont understand how, i dont need an specific approach, but the most clean or efficient as possible.
Thanks in advance if anyone can give me a hint or solution!
EDIT
The problem is im getting undefined in updatedFilteredRestos.
Your reducers should be clean, dumb and all they do should be returning objects. This makes your components more testable and errors easier to catch. In my opinion, this is a perfect use-case for reselect. Here's a medium article: https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c But the true beauty of reselect is that it will memoize for you, i.e. if your states don't change, it uses a cached version of the data.
Anyway, you should clean up your restoReducer to something to this effect.
import { GET_RESTOS, SET_FILTERS } = "../actions/restos";
const initialState = {
restoList: [],
filteredRestos: []
};
const restoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_RESTOS:
return { ...state, restoList: action.payload };
case SET_FILTERS:
return { ...state, filteredRestos: action.payload };
default:
return state;
}
}
Then write your filtered resto selector:
// ../selectors/restos
import { createSelector } from "reselect";
// First, get your redux states
const getRestos = (state) => state.restos.restoList;
const getFilteredRestos = (state) => state.restos.filteredRestos;
// Next, create selectors
export const getFilteredRestoList = createSelector(
[getRestos, getFilteredRestos],
(restoList, filteredRestos) => {
// need to check for non-empty filters
// if it is, simply return the unfiltered `restoList`
if(!Array.isArray(filteredRestos) || !filteredRestos.length)
return restoList || [];
// If you do have valid filters, return filtered logic
return restoList.filter(r => filteredRestos.some(f => f.cityID === r.city_id));
);
Then, use this selector in your components:
// ../components/my-app
import { getFilteredRestoList } from "../selectors/restos";
// hook it up to your `mapStateToProps` as you would a normal state
// except this time, it's a special selector
const mapStateToProps = (state, ownProps) => {
restoList: state.restos.restoList,
filteredRestos: state.restos.filteredRestos,
filteredRestoList: getFilteredRestoList(state) //<-- this is your selector
}
Then inside your component, just reference it: this.props.filteredRestoList.
Let's say I have the next reducer:
export default (state = [], { type, payload }) => {
switch (type) {
case FETCH_POKEMONS:
const objeto = {};
payload.forEach((conexion) => {
objeto[conexion.name] = conexion
});
return objeto;
case FETCH_POKEMON:
return { ...state, ...payload }
default:
return state
}
}
And I will have a combineReducers like this:
export default combineReducers({
pokemons: pokemonReducers,
});
But I want to have pokemons state for the FETCH_POKEMONS actions and another state called pokemon for the FETCH_POKEMON acton. How can I derivate two states in the combineReducers from one reducer file?
This is anti pattern, the closest thing to do here would be export 2 reducers from your file, one for each use case
export const reducer1 = (state = [], { type, payload }) => {
switch (type) {
case FETCH_POKEMONS:
const objeto = {};
payload.forEach((conexion) => {
objeto[conexion.name] = conexion
});
return objeto;
default:
return state
}
}
export const reducer2 = (state = [], { type, payload }) => {
switch (type) {
case FETCH_POKEMON:
return { ...state, ...payload }
default:
return state
}
}
If I'm understanding your question correctly, you have two actions, FETCH_POKEMONS and FETCH_POKEMON, and you want them to update two different states, pokemons and pokemon, respectively.
If these are separate states that don't affect one-another, you'll want to create 2 reducer functions, which I'll call pokemon and pokemons, which each manage their own state and then pass those reducers into combineReducers to combine them into a single application-level reducer. I think this is more likely what you're looking for.
If they are not separate states but instead interconnected properties, then use a single reducer, and give the state 2 properties, pokemon and pokemons, and only update the property you are trying to update in each action (i.e. leave state.pokemon with its previous value when performing FETCH_POKEMONS.
Your action creator seems fine. I am going to post one of my reducers to show how I do it.
import {ONIX_LOGIN_LOADING,ONIX_LOGIN_SUCCESS,ONIX_LOGIN_FAILURE,
ONIX_CONNECTIONS_LOADING,ONIX_CONNECTIONS_SUCCESS,ONIX_CONNECTIONS_FAILURE,
ONIX_PRODUCT_LOADING,ONIX_PRODUCT_SUCCESS,ONIX_PRODUCT_FAILURE
} from "../actions/onix-actions";
const defaultState = {
login:[],
connections: [],
product: []
};
export default function(state = defaultState, action){
switch(action.type){
case ONIX_LOGIN_LOADING:
return {...state, loginLoading:true};
case ONIX_LOGIN_FAILURE:
return {...state, loginLoading:action.isLoaded};
case ONIX_LOGIN_SUCCESS:
return {...state, loginLoading:false, login:action.data};
case ONIX_CONNECTIONS_LOADING:
return {...state, connectionsLoading:true};
case ONIX_CONNECTIONS_FAILURE:
return {...state, connectionsLoading:false};
case ONIX_CONNECTIONS_SUCCESS:
return {...state, connectionsLoading:false, connections:action.data};
case ONIX_PRODUCT_LOADING:
return {...state, productLoading:true};
case ONIX_PRODUCT_FAILURE:
return {...state, productLoading:false, productTitle:false};
case ONIX_PRODUCT_SUCCESS:
return {...state, productLoading:false, product:action.data};
}
return state
}
I like this format, because I can call my own variables off of the state for that part of my reducer. Then in the combine reducers I have the following:
import books from './books';
import onix from './onix';
const rootReducer = combineReducers({
books,
onix
});
export default rootReducer;
Now for all things onix I can call:
this.props.onix.login
or
this.props.onix.productTitle
and it will return the data I want for that part of my project. Did that answer your question?
EDIT: Here is the screenshot of my file structure for reducers
i have a code to update the count value of object. now i need to convert it to immutable.js. i am pretty much new to immutable.js. so how can i convert this code to immutable.js format.
import * as actionTypes from '../actions/actionTypes';
const initialState={
ingredients: {
salad:0,
meat:0,
cheese:0,
},
totalprice:40
}
const ingredientCost = {
cheese:20,
meat:30,
salad:10,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_INGREDIENT:
return {
...state,
ingredients:{
...state.ingredients,
[action.ingredientName] : state.ingredients[action.ingredientName]+1
},
totalprice: state.totalprice + ingredientCost[action.ingredientName],
};
case actionTypes.REMOVE_INGREDIENT:
return {
...state,
ingredients:{
...state.ingredients,
[action.ingredientName] : state.ingredients[action.ingredientName]-1,
},
totalprice: state.totalprice - ingredientCost[action.ingredientName],
};
default:
return state;
}
};
export default reducer;
i tried to change the code to immutable format:
import * as actionTypes from '../actions/actionTypes';
import Immutable from 'immutable';
const initialState=Immutable.fromJS({
ingredients: {
salad:0,
meat:0,
cheese:0,
},
totalprice:40
})
const ingredientCost = {
cheese:20,
meat:30,
salad:10,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_INGREDIENT:
return state.ingredients[action.ingredientName].merge(state.ingredients[action.ingredientName]+1)
case actionTypes.REMOVE_INGREDIENT:
return {
...state,
ingredients:{
...state.ingredients,
[action.ingredientName] : state.ingredients[action.ingredientName]-1,
},
totalprice: state.totalprice - ingredientCost[action.ingredientName],
};
default:
return state;
}
};
export default reducer;
but while doing this i am not able to update the state. i am getting initialstate but i dont know how to update it.
thanks in advance
since initialState is immutable object, You cant use state.ingredients, You should use
state.set() / state.setIn() or
state.update() / state.updateIn() or
state.merge() / state.mergeIn()
read: https://facebook.github.io/immutable-js/docs/#/Map
So I have a reducer:
const buttonReducer = (state = { buttons: [] }, action) => {
switch(action.type)
{
case "add":
{
state = { ...state, buttons: [...state.buttons, action.payload] }
break;
}
case "minus":
{
state = { buttons : state.buttons.filter( item => item !== action.payload ) }
break;
}
default:
break;
}
return state;
};
Now say I have another reducer (we'll call it componentReducer) that looks similar to this, just with the code in the cases changed. Now how do I specify which reducer it should go to after I've done the combineReducers?
const reducers = combineReducers({
component: componentReducer,
button: buttonReducer
});
Will store.component.dispatch(...) work? Or should I just simply rename the cases?
Connection:
const Search = connect(
(store) =>
{
return { number: store.component.number};
}) (SearchComponent);
If you want to use the same dispatch action name for those two reducers, you could use a third variable to the dispatch like dispatch({action: 'add', reducer: 'button', payload: {..your data goes here..}}). Then you would also need to add a conditional to your reducer like this:
const buttonReducer = (state = { buttons: [] }, action) => {
if(action.reducer == 'button'){
switch(action.type){
... your code goes here ...
}
}
Although you could do the above, I recommend you stay away from that solution and stick to naming your dispatch actions according to what they do exactly, like this: ADD_BUTTON and ADD_COMPONENT instead of just add.