Interface:
export interface IClient extends Array<IClient> {
client_name: string
}
Actions:
export const addClientSuccess = createAction(
'[CLIENT] ADD_CLIENT_COMPLETE',
props<{ client_name: IClient }>()
);
Reducers:
export interface ClientState {
client_name: IClient[]
}
export const clientInitialState: ClientState = {
client_name: []
}
export const clientReducer = createReducer(
clientInitialState,
on(ClientActionTypes.addClientSuccess,(state, {client_name}) => ({
...state,
client_name: [...client_name]
})
))
Effects:
addClient = createEffect(() => {
return this.actions.pipe(
ofType(ClientActionTypes.addClient),
switchMap(({ client }) => {
return this.clientService.addClient(client).pipe(
map((res) => ClientActionTypes.addClientSuccess(res)),
catchError(error => {
return of(ClientActionTypes.addClientFailure({ error }))
})
);
})
);
});
With the above code I'm trying to append arrays to client_name but the value I enter gets separated into multiple values inside an array, I have attached the redux store on how it gets iterated.
For example if I have test and testing entered it should print a ['test',"testing"]
youre doing it wrong.
your interface should not be extending Array<IClient>. its a simple string.
export interface IClient {
client_name: string;
}
your state should contain meaningful names: client_name**s**: IClient[]
export interface ClientState {
client_names: IClient[]
}
export const clientInitialState: ClientState = {
client_names: []
}
your reducer should be
a. configured correctly, to have type safety.
b. your client_name array, should contain previous clients initialized before adding a new client. use the spread operator.
export const clientReducer = createReducer(
clientInitialState,
on(ClientActionTypes.addClientSuccess,(state, action) => ({
...state,
client_names: [...state.client_names, action.client_name]
})
your effect should also be configured correctly:
addClient$ = createEffect(() => {
return this.actions.pipe(
ofType(ClientActionTypes.addClient),
switchMap((action) => {
return this.clientService.addClient(action.client_name).pipe(
map((res) => ClientActionTypes.addClientSuccess({ client_name: res})),
catchError(error => {
return of(ClientActionTypes.addClientFailure({ error }))
})
);
})
);
});
I haven't tested this code, so use your intellisense to fix mistypes if any.
sorry on the code instead of code-blocks formatting, SO 4 spaces isn't working properly for some reason.
Related
I've got a problem in a next.js typescript project where i'm using zustand store with persist and immer: https://github.com/pmndrs/zustand
If the toggle_slide_over function is outside of the session_setup object, it works.
If it is inside, it throws runtime error 'not a function'.
Why & how do i fix this?
Interface:
export interface MyAppState {
session_setup: {
slide_over_open: boolean,
toggle_slide_over: (new_toggle_state: boolean) => void,
...
},
session_setup_toggle_slide_over: (new_toggle_state: boolean) => void,
...
}
Function declarations inside doPersist():
const doPersist: () any => { return persist((set) => ({
session_setup: {
...
toggle_slide_over: (new_toggle_state) => set(
produce(state => { state.session_setup.slide_over_open = new_toggle_state })
),
},
session_setup_toggle_slide_over: (new_toggle_state) => set(
produce(state => { state.session_setup.slide_over_open = new_toggle_state })
),
...
How they are retrieved in React:
// When nested:
const toggle_slide_over = useMyAppStore(state => state.session_setup.toggle_slide_over)
// When not nested:
const toggle_slide_over = useMyAppStore(state => state.session_setup_toggle_slide_over)
How they are used:
onClick={() => toggle_slide_over(new_state)}
Store:
const initializeStoreByEnv = (): any => {
if (process.env.NODE_ENV === 'development') return devtools(doPersist())
else return doPersist()
}
export const useMyAppStore = create<MyAppState>()(
initializeStoreByEnv()
)
I've searched for related zustand/immer/redux/functions nested in objects/function not a function at runtime errors but haven't found anything useful, yet...
I'm working around this by just prefixing out-of-object functions with 'session_setup_', which is ok atm, but will get xxxl names with another level of nesting objects.
I completely ran into the same issue. I had nested slices, containing data and functions, which i wanted to persist using zustand/persist. I ended up going with this following approach:
src/zustand/store/index.ts
export const useMyAppStore = create<MyAppState>()(
persist(
(...args) => ({
foo: createFooSlice(...args)
}),
{
name: "app-store",
merge: (persistedState, defaultState) => {
if (!persistedState || typeof persistedState !== "object") {
return defaultState;
}
let resultState: MyAppState = { ...defaultState };
const keys = Object.keys(defaultState) as (keyof MyAppState)[]
keys.forEach((key) => {
if (key in persistedState) {
//#ts-ignore // TypeScript currently don't recognize that key exists in localState
const state = persistedState[key]
if (!!state) {
resultState = { ...resultState, [key]: { ...defaultState[key], ...state }}
}
}
})
return resultState;
}
}
)
I have a React application that is currently using Redux for state management.
What I am trying to achieve: Click a Buy Now button - dispatch a action that makes a request to the server to add the item (increment the cart item count based on server response), check the state to see if the cart item count is greater than 0 & do something if it is.
For some reason, I have to click the button twice in order for the cartItemCount to reflect 1?
My current implementation looks like the below (I have tried to pull out all the unrelated code due to the file being quite large):
CourseSpecificScreen.tsx
const mapStateToProps = (state: RootState) => {
return {
courseSpecificReducer: state.courseSpecificReducer,
authState: state.authReducer,
currencyState: state.currencyReducer,
cartReducer: state.cartReducer,
courseCategoriesState: state.courseCategoriesReducer,
};
};
const mapDispatchTopProps = (dispatch: Dispatch<AnyAction>) => {
return bindActionCreators(ActionCreators, dispatch);
};
const connector = connect(mapStateToProps, mapDispatchTopProps);
type CourseSpecificScreenNavigationProp = CompositeNavigationProp<
StackNavigationProp<ExploreRouteStackParamList, "CourseSpecificScreen">,
CompositeNavigationProp<
StackNavigationProp<AppRouteHeaderParamList>,
StackNavigationProp<AuthRouteStackParamList>
>
>;
type CourseSpecificScreenRouteProp = RouteProp<
ExploreRouteStackParamList,
"CourseSpecificScreen"
>;
type Props = PropsFromRedux & {
navigation: CourseSpecificScreenNavigationProp;
route: CourseSpecificScreenRouteProp;
};
type State = {
cartItemCount: number;
};
class CourseSpecificScreen extends Component<Props, State> {
pruchaseItem = async () => {
const {
courseSpecificReducer,
clearCartAndAddItem,
navigation,
cartReducer,
getCartItemCount,
} = this.props;
const paymentMethod = paymentMethodForDevice();
await clearCartAndAddItem(
paymentMethod,
courseSpecificReducer.productData.code as string,
1,
navigation
)
if(cartReducer.cartItemCount > 0) {
// do some stuff
}
};
render() {
return (
<Button
btnStyle={[this.getStyles().smallButtonBuyCourse]}
labelStyle={[this.getStyles().buttonStickyLabelStyle]}
label={translate(
productData.isBundle && productData.isBundle === true
? "CategorySpecificScreen_buyThisBundle"
: "CategorySpecificScreen_buyThisCourse",
)}
onPress={this.purchaseItem}
disabled={false}
/>
)
};
CourseSpecificScreen.contextType = LocalizationContext;
export default connector(CourseSpecificScreen);
ThunkActions.ts
export const clearCartAndAddItem = (
paymentMethod: string,
productCode: string,
quantity: number,
navigation: any,
): AppThunk => {
return async (dispatch) => {
dispatch(cartActions.updateCartLoadingStatus(true));
const response = await cartServices.clearCart();
const {httpStatusCode} = response as APIResponse;
switch (httpStatusCode) {
case httpStatusCodes.SUCCESS_OK:
case httpStatusCodes.SUCCESS_CREATED:
case httpStatusCodes.SUCCESS_NO_CONTENT:
dispatch(cartActions.updateCartLoadingStatus(false));
dispatch(cartActions.updateCartItemCount(0))
globalConfig.setCartItemCount(0);
dispatch(addItemToCart(paymentMethod, productCode, quantity, navigation));
break;
case httpStatusCodes.CLIENT_ERROR_UNAUTHORIZED:
case httpStatusCodes.SERVER_ERROR_INTERNAL_SERVER_ERROR:
dispatch(cartActions.updateCartLoadingStatus(false));
let alertMessage = "Error, please try again later.";
if (response?.message) alertMessage = response?.message;
Alert.alert("Alert", alertMessage, [
{
text: "Ok",
},
]);
break;
default: {
dispatch(cartActions.updateCartLoadingStatus(false));
}
}
};
};
export const addItemToCart = (
paymentMethod: string,
productCode: string,
quantity: number,
navigation: any,
): AppThunk => {
return async (dispatch) => {
dispatch(cartActions.updateCartLoadingStatus(true));
const response = await cartServices.addItemToCart(productCode, quantity, paymentMethod);
const {httpStatusCode, data, error, message} = response as APIResponse;
console.log('add_item_to_cart_response:', response);
switch (httpStatusCode) {
case httpStatusCodes.SUCCESS_OK:
case httpStatusCodes.SUCCESS_CREATED:
dispatch(cartActions.updateCartLoadingStatus(false));
dispatch(cartActions.updateCartItemCount(quantity));
globalConfig.setCartItemCount(quantity);
break;
case httpStatusCodes.CLIENT_ERROR_UNAUTHORIZED:
dispatch(cartActions.updateCartLoadingStatus(false));
break;
case httpStatusCodes.SERVER_ERROR_INTERNAL_SERVER_ERROR:
case httpStatusCodes.CLIENT_ERROR_BAD_REQUEST:
dispatch(cartActions.updateCartLoadingStatus(false));
Alert.alert("Alert", (message)? message : "Error, it looks like you already have access to this course.", [
{
text: "Ok",
},
]);
break;
default: {
dispatch(cartActions.updateCartLoadingStatus(false));
}
}
};
};
Reducers.ts
const initialState: CartInitialState = {
isLoading: true,
cartToken: "",
responseStatus: apiResponseStatuses.IDLE,
cartItemCount: 0,
isMessageVisible: false,
message: "",
};
export default function cartReducer(
state = initialState,
action: CartActionTypes,
): CartInitialState {
switch (action.type) {
case UPDATE_LOADING_STATUS:
return {
...state,
isLoading: action.isLoading,
};
case UPDATE_CART_TOKEN:
return {
...state,
cartToken: action.cartToken,
};
case UPDATE_RESPONSE_STATUS:
return {
...state,
responseStatus: action.responseStatus,
};
case UPDATE_CART_ITEM_COUNT_TOKEN:
return {
...state,
cartItemCount: action.cartItemCount,
};
case CLEAR_DATA_ON_LOGOUT:
return {
...state,
isLoading: true,
cartToken: "",
responseStatus: apiResponseStatuses.IDLE,
cartItemCount: 0,
isMessageVisible: false,
message: "",
};
default: {
return state;
}
}
}
In the pruchaseItem() function of CourseSpecificScreen.tsx, I would like to dispatch a action that adds the item to the cart and immediately afterwards check if the cartItemCount has been updated & if it has, do something... This functionality works as expected, but only after clicking the Buy Now button twice.
I have ruled out the possibility of the issue being the API request failing the first time.
I have been stuck on this issue for several days now so any help or advice would be greatly appreciated. Let me know if I need to include more information
In my case, I was storing a reference of the old cartReducer state before it was being updated.
I got this working by updating my purchaseItem() function to look like the below:
pruchaseItem = async () => {
const {
courseSpecificReducer,
clearCartAndAddItem,
navigation
} = this.props;
const paymentMethod = paymentMethodForDevice();
await clearCartAndAddItem(
paymentMethod,
courseSpecificReducer.productData.code as string,
1,
navigation
)
const { cartReducer } = this.props;
if(cartReducer.cartItemCount > 0) {
// do some stuff
}
};
Im playing around with recoil for the first time and cant figure out how I can read all elements from an atomFamily. Let's say I have an app where a user can add meals:
export const meals = atomFamily({
key: "meals",
default: {}
});
And I can initialize a meal as follows:
const [meal, setMeal] = useRecoilState(meals("bananas"));
const bananas = setMeal({name: "bananas", price: 5});
How can I read all items which have been added to this atomFamily?
You have to track all ids of the atomFamily to get all members of the family.
Keep in mind that this is not really a list, more like a map.
Something like this should get you going.
// atomFamily
const meals = atomFamily({
key: "meals",
default: {}
});
const mealIds = atom({
key: "mealsIds",
default: []
});
When creating a new objects inside the family you also have to update the mealIds atom.
I usually use a useRecoilCallback hook to sync this together
const createMeal = useRecoilCallback(({ set }) => (mealId, price) => {
set(mealIds, currVal => [...currVal, mealId]);
set(meals(mealId), {name: mealId, price});
}, []);
This way you can create a meal by calling:
createMeal("bananas", 5);
And get all ids via:
const ids = useRecoilValue(mealIds);
Instead of using useRecoilCallback you can abstract it with selectorFamily.
// atomFamily
const mealsAtom = atomFamily({
key: "meals",
default: {}
});
const mealIds = atom({
key: "mealsIds",
default: []
});
// abstraction
const meals = selectorFamily({
key: "meals-access",
get: (id) => ({ get }) => {
const atom = get(mealsAtom(id));
return atom;
},
set: (id) => ({set}, meal) => {
set(mealsAtom(id), meal);
set(mealIds (id), prev => [...prev, meal.id)]);
}
});
Further more, in case you would like to support reset you can use the following code:
// atomFamily
const mealsAtom = atomFamily({
key: "meals",
default: {}
});
const mealIds = atom({
key: "mealsIds",
default: []
});
// abstraction
const meals = selectorFamily({
key: "meals-access",
get: (id) => ({ get }) => {
const atom = get(mealsAtom(id));
return atom;
},
set: (id) => ({set, reset}, meal) => {
if(meal instanceof DefaultValue) {
// DefaultValue means reset context
reset(mealsAtom(id));
reset(mealIds (id));
return;
}
set(mealsAtom(id), meal);
set(mealIds (id), prev => [...prev, meal.id)]);
}
});
If you're using Typescript you can make it more elegant by using the following guard.
import { DefaultValue } from 'recoil';
export const guardRecoilDefaultValue = (
candidate: unknown
): candidate is DefaultValue => {
if (candidate instanceof DefaultValue) return true;
return false;
};
Using this guard with Typescript will look something like:
// atomFamily
const mealsAtom = atomFamily<IMeal, number>({
key: "meals",
default: {}
});
const mealIds = atom<number[]>({
key: "mealsIds",
default: []
});
// abstraction
const meals = selectorFamily<IMeal, number>({
key: "meals-access",
get: (id) => ({ get }) => {
const atom = get(mealsAtom(id));
return atom;
},
set: (id) => ({set, reset}, meal) => {
if (guardRecoilDefaultValue(meal)) {
// DefaultValue means reset context
reset(mealsAtom(id));
reset(mealIds (id));
return;
}
// from this line you got IMeal (not IMeal | DefaultValue)
set(mealsAtom(id), meal);
set(mealIds (id), prev => [...prev, meal.id)]);
}
});
You can use an atom to track the ids of each atom in the atomFamily. Then use a selectorFamily or a custom function to update the atom with the list of ids when a new atom is added or deleted from the atomFamily. Then, the atom with the list of ids can be used to extract each of the atoms by their id from the selectorFamily.
// File for managing state
//Atom Family
export const mealsAtom = atomFamily({
key: "meals",
default: {},
});
//Atom ids list
export const mealsIds = atom({
key: "mealsIds",
default: [],
});
This is how the selectorFamily looks like:
// File for managing state
export const mealsSelector = selectorFamily({
key: "mealsSelector",
get: (mealId) => ({get}) => {
return get(meals(mealId));
},
set: (mealId) => ({set, reset}, newMeal) => {
// if 'newMeal' is an instance of Default value,
// the 'set' method will delete the atom from the atomFamily.
if (newMeal instanceof DefaultValue) {
// reset method deletes the atom from atomFamily. Then update ids list.
reset(mealsAtom(mealId));
set(mealsIds, (prevValue) => prevValue.filter((id) => id !== mealId));
} else {
// creates the atom and update the ids list
set(mealsAtom(mealId), newMeal);
set(mealsIds, (prev) => [...prev, mealId]);
}
},
});
Now, how do you use all this?
Create a meal:
In this case i'm using current timestamp as the atom id with Math.random()
// Component to consume state
import {mealsSelector} from "your/path";
import {useSetRecoilState} from "recoil";
const setMeal = useSetRecoilState(mealsSelector(Math.random()));
setMeal({
name: "banana",
price: 5,
});
Delete a meal:
// Component to consume state
import {mealsSelector} from "your/path";
import {DefaultValue, useSetRecoilState} from "recoil";
const setMeal = useSetRecoilState(mealsSelector(mealId));
setMeal(new DefaultValue());
Get all atoms from atomFamily:
Loop the list of ids and render Meals components that receive the id as props and use it to get the state for each atom.
// Component to consume state, parent of Meals component
import {mealsIds} from "your/path";
import {useRecoilValue} from "recoil";
const mealIdsList = useRecoilValue(mealsIds);
//Inside the return function:
return(
{mealIdsList.slice()
.map((mealId) => (
<MealComponent
key={mealId}
id={mealId}
/>
))}
);
// Meal component to consume state
import {mealsSelector} from "your/path";
import {useRecoilValue} from "recoil";
const meal = useRecoilValue(mealsSelector(props.id));
Then, you have a list of components for Meals, each with their own state from the atomFamily.
Here is how I have it working on my current project:
(For context this is a dynamic form created from an array of field option objects. The form values are submitted via a graphql mutation so we only want the minimal set of changes made. The form is therefore built up as the user edits fields)
import { atom, atomFamily, DefaultValue, selectorFamily } from 'recoil';
type PossibleFormValue = string | null | undefined;
export const fieldStateAtom = atomFamily<PossibleFormValue, string>({
key: 'fieldState',
default: undefined,
});
export const fieldIdsAtom = atom<string[]>({
key: 'fieldIds',
default: [],
});
export const fieldStateSelector = selectorFamily<PossibleFormValue, string>({
key: 'fieldStateSelector',
get: (fieldId) => ({ get }) => get(fieldStateAtom(fieldId)),
set: (fieldId) => ({ set, get }, fieldValue) => {
set(fieldStateAtom(fieldId), fieldValue);
const fieldIds = get(fieldIdsAtom);
if (!fieldIds.includes(fieldId)) {
set(fieldIdsAtom, (prev) => [...prev, fieldId]);
}
},
});
export const formStateSelector = selectorFamily<
Record<string, PossibleFormValue>,
string[]
>({
key: 'formStateSelector',
get: (fieldIds) => ({ get }) => {
return fieldIds.reduce<Record<string, PossibleFormValue>>(
(result, fieldId) => {
const fieldValue = get(fieldStateAtom(fieldId));
return {
...result,
[fieldId]: fieldValue,
};
},
{},
);
},
set: (fieldIds) => ({ get, set, reset }, newValue) => {
if (newValue instanceof DefaultValue) {
reset(fieldIdsAtom);
const fieldIds = get(fieldIdsAtom);
fieldIds.forEach((fieldId) => reset(fieldStateAtom(fieldId)));
} else {
set(fieldIdsAtom, Object.keys(newValue));
fieldIds.forEach((fieldId) => {
set(fieldStateAtom(fieldId), newValue[fieldId]);
});
}
},
});
The atoms are selectors are used in 3 places in the app:
In the field component:
...
const localValue = useRecoilValue(fieldStateAtom(fieldId));
const setFieldValue = useSetRecoilState(fieldStateSelector(fieldId));
...
In the save-handling component (although this could be simpler in a form with an explicit submit button):
...
const fieldIds = useRecoilValue(fieldIdsAtom);
const formState = useRecoilValue(formStateSelector(fieldIds));
...
And in another component that handles form actions, including form reset:
...
const resetFormState = useResetRecoilState(formStateSelector([]));
...
const handleDiscard = React.useCallback(() => {
...
resetFormState();
...
}, [..., resetFormState]);
I have an issue with update new data by using react-redux, Add and remove are working fine, but it will return null object when i want to edit one of the data.
I am not sure what stage cause wrong.
action.users.js
import { v4 as uuid } from 'uuid';
// ADD_USER
export const addUser = ({ username = '', location = '' } = {}) => ({
type: 'ADD_USER',
user: { id: uuid(), username, location },
});
//REMOVE_USER
export const removeUser = ({ id } = {}) => ({ type: 'REMOVE_USER', id });
//EDIT_USER
export const editUser = ({ id, updates } = {}) => ({
type: 'EDIT_USER',
id,
updates,
});
components.EditUserPage.js
import React from 'react';
import { connect } from 'react-redux';
import UserForm from './UserForm';
import { editUser } from '../actions/users';
const EditUserPage = props => {
return (
<div>
<UserForm
user={props.user}
onSubmit={user => {
props.dispatch(editUser(props.user.id, user));
props.history.push('/Users');
}}
/>
</div>
);
};
const mapStateToProps = (state, props) => {
return {
user: state.user.find(user => user.id === props.match.params.id),
};
};
export default connect(mapStateToProps)(EditUserPage);
reducers.users.js
const usersReducerDefaultState = [];
export default (state = usersReducerDefaultState, action) => {
switch (action.type) {
case 'ADD_USER':
return [...state, action.user];
case 'REMOVE_USER':
return state.filter(({ id }) => id !== action.id);
case 'EDIT_USER':
return state.map(user => {
if (user.id === action.id) {
return {
...user,
...action.updates,
};
} else {
return user;
}
});
default:
return state;
}
};
The page should go to ./edit when i click one of the data and the input value will show the currently selected data in the userform component. it seems like going well at this stage, i change the input value and click the create button, the page back to /User, unfortunately, the selected data return null object. please help me. you answer will help me to jump out of this nightmare.
Change
props.dispatch(editUser(props.user.id, user));
to
props.dispatch(editUser({id: props.user.id, updates: user}));
You define the method signature of edit user as
function editUser ({ id, updates } = {}) {…}
That is, a function that takes one (optional) argument. That argument is expected to be an object with an id and updates property. (I don't think you should make the argument optional. It is needed for the rest of the function to work.)
However, you call the function with two arguments, presumable a number and an object. Also, if props.user.id ends up being undefined, then it will be replaced with the default value of {}, and id and updates will be undefined, but no error will occur (which is what you want, because you are passing the wrong type to the function).
Alternatively, you could define the method signature to take to positional arguments and not change the function call:
const editUser = (id, updates) => (…)
or, if your UserForm component includes id in the call to onSubmit:
const editUser = ({ id, ...updates }) => (…) // use destructuring to select the id from the argument
// call it like this:
props.dispatch(editUser({user}));
i try to store multiple object in redux store on my react native app, but only one object is save,
i'm new at redux, i try a lot of solutions found on StackOverflow but no one works :/
result i have in my store:
"hives": {"hive_id": 12944, "hive_name": null}
result i want (or something like that) :
"hives": [
1: {"hive_id": 123, "hive_name": "HelloHive"},
2: {"hive_id": 12944, "hive_name": null}]
store:
const middleware = [thunk]
export const store = createStore(persistedReducer, applyMiddleware(...middleware));
export const persistor = persistStore(store);
reducer :
const INIT_STATE = {
hives: [],
}
const hiveReducer = (state = INIT_STATE, action) => {
switch (action.type) {
case SET_HIVES:
return {
...state,
hives: action.payload,
};
[...]
action creator:
export const setHives = hives => {
return {
type: SET_HIVES,
payload: hives,
};
};
action:
export const getHives = () => {
return dispatch => {
axios.get(GET_HIVE_URL, HEADER).then(res => {
const status = res.data.status;
const hives = res.data.hives;
if (status == 'hiveFound') {
for (let i = 0; i < hives.length; i++) {
console.log(hives[i]);
dispatch(setHives(hives[i]));
}
}
});
};
};
and my API send me:
"hives": [
{
"hive_id": 123,
"hive_name": "HelloHive"
},
{
"hive_id": 12944,
"hive_name": null
}
]
and console.log(hives[i]) return :
LOG {"hive_id": 123, "hive_name": "HelloHive"}
LOG {"hive_id": 12944, "hive_name": null}
thanks you
First of all, in your reducer you don't need to use ...state spread operator, since hives seems to be the only one variable in your state there. And second, you are iterating over each element of hives, therefore you are inputting them one by one thus overwriting the previous one. You are not appending it to array. Here's how you need to change your action:
export const getHives = () => {
return dispatch => {
axios.get(GET_HIVE_URL, HEADER).then(res => {
const status = res.data.status;
const hives = res.data.hives;
if (status == 'hiveFound') {
dispatch(setHives(hives));
}
});
};
};
This way it will write the whole array into that variable in redux.
You can try this below so you can store the whole array. assuming you already have the actions.
InitialState
export default {
hives:[]
}
HivesReducer
export default function counter(state = initialState.hives, action) {
switch (action.type) {
case Types.SET_HIVES:
return [...state, action.payload];
default:
return state;
}
}
In your reducer try this :
case SET_HIVES:
return {
...state,
hives: [...state.hives,action.payload],
};
[...]
hope it helps. feel free for doubts