React Redux how to add data to object's array - reactjs

My user structure is:
user = {
email: 'email',
flashcards: []
}
And i would like to add data into user's flashcards array (using redux)
My user-reducer
import { UserActionTypes } from './user.types';
const INITIAL_STATE = {
currentUser: null,
};
// GETS STATES OBJ AND RECIVES AN ACTION
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case UserActionTypes.SET_CURRENT_USER:
return {
...state,
currentUser: action.payload,
};
case UserActionTypes.ADD_FLASHCARD:
return {
...state,
currentUser: action.payload,
};
default:
return state;
}
};
export default userReducer;
user-actions
export const addFlashCard = user => ({
type: UserActionTypes.ADD_FLASHCARD,
payload: user.flashcards,
});
And when i'm doing so my payload is undefined.
Could you give me some hints?

You are currently overwriting currentUser with the value of user.flashcards from the redux action. To add new flashcards, the ADD_FLASHCARD branch of your reducer should look more like this:
case UserActionTypes.ADD_FLASHCARD:
return {
...state,
currentUser: {
...state.currentUser,
flashcards: [
...state.currentUser.flashcards,
...action.payload
]
}
};

Related

react redux- updating state

I need a help with updating user state.I register a user with name, email and password. Then in the profile page i want to give a chance to update(or create new) values like City and Country. And now im confused. My Redux action
export const updateUser=(profileId, updatedUser)=>async(dispatch)=>{
try {
const {data}= await api.updateUser(profileId,updatedUser)
dispatch({type: UPDATE_USER, payload: data})
} catch (error) {
console.log(error.message);
}
Reducer:
const initialState = {
users: [
{
city: "", country: "", email: "", name: "",
password: "",
_id: "",
},
],
};
const user = (state = initialState, action) => {
switch (action.type) {
case GET_ONE_USER:
return {
...state,
users: action.payload,
};
case UPDATE_USER:
return { ...state, users: action.payload };
default:
return state;
}
};
API:
export const updateUser=(profileId, updatedUser)=>API.patch(`/user/${profileId}`, updatedUser)
route:
router.patch('/:profileId',updateUser)
controller:
export const updateUser = async (req,res)=>{
const {id} = req.params
const {city, country} = req.body
const updatedUser={city, country}
try {
await User.findByIdAndUpdate(id,updatedUser, {new: true} )
res.status(200).json(updatedUser)
} catch (error) {
res.status(400).json({message: 'Blad'})
}
}
In my component:
const{ users} = useSelector((state)=>state.users)
and submit handler const handleSubmit =(e) =>{ e.preventDefault() dispatch(updateUser(users._id, data)) }
When i click button and dispatch an action, it only changes new values, all other are removed. I think that has something to do with my return state from reducer?
EDIT:
Ok, somehow i fixed this, although i think i could simplify the code?
case UPDATE_USER:
return { ...state, users: {...state.users, city:action.payload.city, country:action.payload.country}};
default:
return state;
You are updating state in a wrong way. You are replacing state with the new payload only. What you have to do is you have to keep previous state data and then add new payload that you are getting.
switch (action.type) {
.....
case UPDATE_USER:
return { ...state, users: [...state.users, action.payload] };
default:
return state;
}
};

(Redux typscript react) type initial store has no properties in common with type store

Hello i've been struggling with this for a while
i want to start with a initial state like so:
const initialState = {
cart: { cartItems: {}}
};
but i already have a couple reducers that make my initial state like so:
{
productList?:undefined,
productDetails?:undefined
}
but i get the error:
Type '{cart:{cartItems:{}}' has no properties in common with type '{productList?: undefined, productDetails? :undefined}'
my reducer looks like:
export const productListReducer = (state = { products: [] as any[] }, action:productListReducer) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return {
loading: true,
products: [] as any[],
};
case PRODUCT_LIST_SUCCESS:
return {
loading: false,
products: action.payload,
};
case PRODUCT_LIST_FAIL:
return {
loading: false,
error: action.payload,
};
default:
return state;
}
};
const reducer = combineReducers({
productList: productListReducer,
});
const store = createStore(
reducer,
initialState,
composeEnhancers(applyMiddleware(thunk))
);
any help is appreciated, thanks

react-redux store data - reducer setter

I'm trying to store a dictionary in react-redux in react-native.
So my action looks like this :
let data = {};
export const setData = (pData) => ({
type: 'SET',
data: pData,//I don't know how to store the data in data declared in parent
});
export const getData = () => ({
type: 'GET',
data: data,
});
And my reducer looks like this :
const items = (state = [], action) => {
switch (action.type) {
case 'SET':
return [
//I don't know how to set the data here
];
case 'GET':
return state;
default:
return null;
}
};
export default items;
I looked in many tutorial on YouTube, they just you need to paste this, and boom.
If I get cleared with one dictionary, I think I can work with others.
This part almost right. You don't need "GET" to get data and this part let data = {} should be in reducer;
export const setData = (pData) => ({
type: 'SET',
data: pData,
});
/*
export const getData = () => ({
type: 'GET',
data: data,
});
*/
Reducer
const initState = {
data:[],
anotherSate:[]
}
const rootReducer = (state = initState, action) => {
switch(action.type){
case 'SET': {
return {
...state, // if you have more states
data: [action.data, ...state.data]
}
}
default:
return state;
}
}
export default rootReducer;
You can get your "Data". "New" component
//Your component code
//...
this.props.data // here is your "data"
//...
const mapStateToProps = (state) => {
return {
data: state.data,
}
}
export default connect(mapStateToProps)(NewComponent);
In order to check if your Reducer works, try to add something in your initState and extract the data in NewComponent
const items = (state = [], action) => {
switch (action.type) {
case 'SET':
return {
...state, // adding the previous state first
data: action.data // here data can be any keyword you want to save your dictionary in
}
case 'GET':
return state;
default:
return null;
}
};

Why is my reducer returning undefined, React TypeScript

I am trying to setup my Redux store with React and TypeScript but it gives me an error that my auth reducer is undefined.
This is my store.ts:
import {Action, applyMiddleware, combineReducers, compose, createStore} from 'redux';
import { auth, IAuthState } from './Auth/reducer';
import { general, IGeneralState } from './General/reducer';
export interface IAppState {
auth: IAuthState;
general: IGeneralState;
}
export const rootReducer = () => combineReducers({
auth: auth,
general: general,
});
const store = createStore<IAppState, Action<any>, {}, {}>(
rootReducer(),
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
);
export { store };
This is my auth reducer:
import { User } from '../../interfaces/user.interface';
import { AuthActionTypes } from './actions';
export interface IAuthState {
user: User;
authenticated: boolean;
}
const initialState: IAuthState = {
user: null,
authenticated: true,
};
export const auth = (state: IAuthState = initialState, action: any): IAuthState => {
switch (action.type) {
case AuthActionTypes.Setuser:
const { User } = action.payload;
return {
...state,
user: User
};
case AuthActionTypes.Logout:
return {
...state,
user: null,
authenticated: false,
};
}
};
It gives me the error:
Uncaught Error: Reducer "auth" returned undefined during
initialization. If the state passed to the reducer is undefined, you
must explicitly return the initial state. The initial state may not be
undefined. If you don't want to set a value for this reducer, you can
use null instead of undefined.
Only thing what you need to do is always return a value from the reducer, even if it is null.
The following fix will do the job:
export const auth = (state: IAuthState = initialState, action: any): IAuthState => {
switch (action.type) {
case AuthActionTypes.Setuser:
const { User } = action.payload;
return {
...state,
user: User
};
case AuthActionTypes.Logout:
return {
...state,
user: null,
authenticated: false,
};
}
// this step was missing
return state;
};
Few rules what you need to follow:
Always needs to be returning state, even if you did not change anything, even the value is just null.
You should not have return undefined.
If the state has changed, you need to replace it e.g.: {...state, newValue: false}.
From documentation:
We return the previous state in the default case. It's important to return the previous state for any unknown action.
Read further: Handling Actions
I hope this helps!
Reducer is a simple function that returns state no matter if it has changed or not. You are missing the default case in your reducer so just replace it with below :
export const auth = (state: IAuthState = initialState, action: any): IAuthState => {
switch (action.type) {
case AuthActionTypes.Setuser:
const { User } = action.payload;
return {
...state,
user: User
};
case AuthActionTypes.Logout:
return {
...state,
user: null,
authenticated: false,
};
default: // this are 2 lines ive added
return state
}
};
Hope it helps. feel free for doubts
You forgot to add the default case.
export const auth = (state: IAuthState = initialState, action: any): IAuthState => {
switch (action.type) {
case AuthActionTypes.Setuser:
const { User } = action.payload;
return {
...state,
user: User
};
case AuthActionTypes.Logout:
return {
...state,
user: null,
authenticated: false,
};
default:
return state;
}
};

REACT can't do object assign in reducer

I got a problem when do objectAssign to change the state in store into a new data from server, It always get a null as the result.
i call my action in onEnter function(React-Router)
export function GET_SetupTabTitles() {
store.dispatch(getSetupTabTitles());
}
this is my action :
import {
TOGGLE_DRAWER_IN_APPBAR,
GET_SETUP_TAB_TITLES,
} from '../constants/actionTypes';
import axios from 'axios';
const ROOT_URL = 'http://localhost:8000';
export function toggleDrawerInAppBar(open){
return { type: TOGGLE_DRAWER_IN_APPBAR, openStatus: open }
}
export function getSetupTabTitles(){
return function(dispatch){
axios.get(`${ROOT_URL}/api/component/getSetupTabTitles`)
.then(response => {
dispatch({type: GET_SETUP_TAB_TITLES,
payload: response
});
});
}
}
this is my initial state on reducer :
export default {
auth: {
authenticated: (localStorage.getItem('laravel_user_token') !== null),
userinfo: {
name: null
},
error:""
},
comp: {
openDrawerStatus: false,
setupTabTitles: null,
}
};
and this is my reducer :
import {
TOGGLE_DRAWER_IN_APPBAR,
GET_SETUP_TAB_TITLES,
} from '../constants/actionTypes';
import initialState from './initialState';
import objectAssign from 'object-assign';
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
case TOGGLE_DRAWER_IN_APPBAR:
return objectAssign({}, state, {openDrawerStatus: action.openStatus});
case GET_SETUP_TAB_TITLES:
console.log(action.payload.data);
return objectAssign({}, state, {setupTabTitles: action.payload.data});
default:
return state;
}
};
export default compReducer;
when i do console.log inside
case GET_SETUP_TAB_TITLES:
it show :
Array[2]0: 0:Object 1:Object
On using JSON.stringify() it shows me [{"tabTitle":"Events"},{"tabTitle":"Tasks"}]
but my state (setupTabTitles) didn't change at all.
i do try this one :
case GET_SETUP_TAB_TITLES:
state.setupTabTitles.push(action.payload.data[0]);
return state;
it work, but i don't want to direct change the state.
You don't need to import ojectAssign from 'object-assign'; when you make use of the current ES6 syntax in your code. You only need Object.assign. Also since your action.data.payload is an array and you need to append to an array you can use the spread operator like
return {
...state,
setupTabTitles: [...state.setupTabTitles, action.payload.data]
}
Also you need to initialise you componentState to be an empty array and not null or undefined. Change that to below code
export default {
auth: {
authenticated: (localStorage.getItem('laravel_user_token') !== null),
userinfo: {
name: null
},
error:""
},
comp: {
openDrawerStatus: false,
setupTabTitles: [],
}
};
Try it like below
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
case TOGGLE_DRAWER_IN_APPBAR:
return Object.assign({}, state, {openDrawerStatus: action.openStatus});
case GET_SETUP_TAB_TITLES:
console.log(action.payload.data);
return {
...state,
setupTabTitles: [...state.setupTabTitles, ...action.payload.data]
}
default:
return state;
}
};
The syntax of objectAssign is different from what I use, you can see it here
var state = {
openDrawerStatus: false,
setupTabTitles: [],
}
var payload = [{"tabTitle":"Events"},{"tabTitle":"Tasks"}]
console.log( {
...state,
setupTabTitles: [...state.setupTabTitles, ...payload]
});
As you are already using ES6, you could just use the object spread operator and get rid of the object-assign library, it would be like this:
import {
TOGGLE_DRAWER_IN_APPBAR,
GET_SETUP_TAB_TITLES,
} from '../constants/actionTypes';
import initialState from './initialState';
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
case TOGGLE_DRAWER_IN_APPBAR:
return { ...state, openDrawerStatus: action.openStatus };
case GET_SETUP_TAB_TITLES:
return { ...state, setupTabTitles: action.payload.data };
default:
return state;
}
};
export default compReducer;
In your initial state, I would change setupTabTitle from null to an empty array []:
setupTabTitles: [],
And in your reducer, append data to this array:
const compReducer = (state = initialState.comp, action) => {
switch (action.type) {
...
case GET_SETUP_TAB_TITLES:
return {
...state,
setupTabTitles: [
...state.setupTabTitles,
...action.payload.data
]
}
...
}
};
Or if you don't want to append, just replace, I would do:
setupTabTitles: [
...action.payload.data
]

Resources