React-Redux: State updated but not copied - javascript

In my reducer I can't understund why my state returns updated but not copied.
const loginReducer = (state = {}, action) => {
let newstate = JSON.parse(JSON.stringify(state))
const { type, payload } = action
switch(type) {
case constant.LOGIN_SUCCESS:
//
newstate = { login: [{ user: payload.user, password: payload.password, logged: true }] }
console.log(newstate.login)
break;
default:
break;
}
return newstate
}
Surely it's a simple thing what I'm missing, but I can't figure out it

It looks as though you are completely re-assigning newstate in this line:
newstate = { login: [{ user: payload.user, password: payload.password, logged: true }] }
If you want to maintain the current values in state you need to also include them:
newstate = { ...newstate, login: [{ user: payload.user, password: payload.password, logged: true }] }
The above uses object spread, which you may or may not have in your babel configuration. You can also use Object.assign to achieve the same thing:
newstate = Object.assign(newstate, { login: [{ user: payload.user, password: payload.password, logged: true }] });

Related

Using spread operator to add new value to key value pair in object

Struggling to figure out how to add a new item to an object from my reducer. This is the payload I am receiving GH
This is the relevant code in my reducer
const initialForm = {
nationality: '',
}
function addName(state= initialForm, action) {
switch(action.type){
case ADD_NEW_NATIONALITY:
console.log('nationality',action.payload.name)
return {
...state,
[action.payload.nationality]: action.payload.value
}
default:
return state
}
}
and my action creators
export const addFullName = newName => dispatch => {
console.log(newName)
axios.get(`https://api.nationalize.io?name=${newName.name}`)
.then(res => {
dispatch({ type: ADD_NEW_NATIONALITY, payload: res.data.country[0].country_id})
})
Please help me figure this out
Your payload is just a plain value (not an object), so you cannot find action.payload.name and action.payload.value
And similarly, you cannot find action.payload.nationality either.
Here is how your action looks like
{ type: ADD_NEW_NATIONALITY, payload: 1} //`1` is country_id
To set nationality correctly, you can check the below implementation
const initialForm = {
nationality: '',
}
function addName(state= initialForm, action) {
switch(action.type){
case ADD_NEW_NATIONALITY:
return {
...state,
nationality: action.payload //update your `nationality` property with a plain value from `payload`
}
default:
return state
}
}

How to set object state in react

I have the following code:
this.state = {
user: null
}
If the user has a name property how would I set it knowing that this.setState({user.name: 'Bob') won't work.
this.setState({ user: {...this.state.user, name: 'Bob'} });
const copy = {...this.state.user}
copy.name = 'Bob'
this.setState({ user: copy })
and if you don't want to override existing properties like say age do this
const copy = {...this.state.user}
copy.name = 'Bob'
this.setState({ user: { ...user, name: copy } })
user itself is probably an object so you can set the state of user to an object
this.setState({ user: { name: "bob" }})
or
this.state = {
user: {
name: "bob"
}
}
If your state contains an object, then you can update the value of nested state using the javascript spread operator.
Other user parameters will not be affected.
this.setState({
...this.state,
user: {
...this.state.user,
name:'Bob'
}
})
You can use spread operator for this like:
this.setState(prevState => ({
user: { ...prevState.user, name: 'Bob' }
}))
For more info:
Using State Correctly

useReducer React approach

I'm playing with useReducer react's api, and wonder to know the difference between the theoretical (documentation) part and one I implement.
intialState of commponent with useReducer hook:
const [fields, dispatch] = React.useReducer(formReducer, {
firstName: { value: '', isValid: false },
lastName: { value: '', isValid: false },
});
theoretical Variant
const formActionTypes = {
firstName: 'FIRST_NAME',
lastName: 'LAST_NAME',
};
....
function formReducer(state, action) {
switch (action.type) {
case formActionTypes.firstName:
return { ...state, firstName: { ...action.payload } };
case formActionTypes.lastName:
return { ...state, lastName: { ...action.payload } };
default:
return state;
}
}
....
dispatch({
type: formActionTypes[name], //name is input name
payload: { value, isValid } //value is e.target.value
});
MY implimentation
function formReducer(state, action) {
return { ...state, [action.name]: { ...action.payload } };
}
....
dispatch({
name, //name is input name
payload: { value, isValid } //value is e.target.value
});
The two reducers you've shown will both work and produce identical results if that's what you're asking. I think the theoretical version version you're getting from the documentation is meant to demonstrate a particular concept, though, which your reducer arguably violates (though it's not a big deal; our job is to make working code, not to pass some purity test!).
Specifically, you typically want to somewhat decouple actions from state. The action shouldn't just be a mirror of your state data structure; if you want that coupling, you'd might as well use useState instead and just set the state directly. A reducer is meant to decouple this by you modeling a description of the action, and then it's only the reducer that decides how that action acts on state. You might, for example, decide to add a clear form button. With your current pattern, you'd have to dispatch two actions, which would cause two state updates, because your actions closely model the state. The switch statement pattern allows you to easily apply different types of logic based on different types of actions.
There are no wrong answers, just different approaches all with their merits. Here's one that I think introduces better decoupling by letting the reducer logic take care of knowing about whether a field is valid:
const SET_FIRST_NAME = Symbol();
const SET_LAST_NAME = Symbol();
const CLEAR_FORM = Symbol();
// Call action creators instead, like: dispatch(setFirstName(e.target.value));
const setFirstName = name => { type: SET_FIRST_NAME, value: name };
const setLastName = name => { type: SET_LAST_NAME, value: name };
const clearForm = () => { type: CLEAR_FORM };
const initialState = {
firstName: { value: '', isValid: false },
lastName: { value: '', isValid: false }
};
const notEmpty = value => !!(value && value.trim().length);
const validateFirstName = notEmpty; // Or replace with different logic
const validateLastName = notEmpty;
const reducer = (state, action) => {
switch (action.type) {
case SET_FIRST_NAME:
return {
...state,
firstName: {
value: action.value,
isValid: validateFirstName(value)
}
}
case SET_LAST_NAME:
return {
...state,
lastName: {
value: action.value,
isValid: validateLastName(value)
}
}
case CLEAR_FORM:
return initialState;
default:
return state;
}
};

Updating object inside redux state throws an error

I'm trying to update array inside object in my reducer.
const initialState = {
group: {
name: "",
date: "",
description: "",
users: [],
posts: []
},
morePosts: false,
groups: []
};
export function groups(state = initialState, action) {
switch (action.type) {
.......
case REQUEST_MORE_POSTS:
{
return {
...state,
group:{
...state.group,
posts: [
...state.group.posts,
...action.payload.posts
]
},
morePosts: action.payload.morePosts
}
}
case ADD_NEW_POST:
{
return {
...state,
group:{
...state.group,
posts: [
action.payload,
...state.group.posts
]
}
}
}
........
default:
return state;
}
}
Unfortunately in both cases I get an error:
It works when I extract posts out of my group object but I need it inside.
I can't figure out what I've done wrong here. Can someone point me to the right direction?
Here is an action creator for adding new post.
export function addPost(url, payload) {
return function(dispatch) {
axios.post(url + "php/addPostGroup.php", {payload}).then(response => {
dispatch({
type: ADD_NEW_POST,
payload: response.data.post
})
})
}
}
response.data.post is a simple object.
I've added console.log() before dispatch. This is how my response looks like:
Alright I solved it. Before fetching any data my state.group.posts array was for some reason treated as undefined. I had to manually declare it as empty array using
var posts = state.group.posts != undefined ? state.group.posts:[];

react redux merge state with given array

Lets say I have a reducer which is like :
const initialState = [
{
accessToken: null,
isLoggedIn: false,
}
]
export default function my_reducer(state = initialState, action) {
switch (action.type) {
case LOGIN:
return state.merge(user: action) ---> how to handle this
and the output should be like:
[
{
accessToken: null,
isLoggedIn: false,
user: {
name: 'some name',
email: 'some email'
}
]
In action I am getting a array which I am providing by doing JSON.stringify(response)
previous data should not be changed and new data should be updated
The ES6 Way
To create a new object with the state in ES6 we can use the spread operator. Like this
...
case ActionType.SUCCESS_GET_DATA : {
let newState = { ...state, [action.uniqueKey]: action.payload };
return state.merge(newState);
}
...
I did the uniqueKey part as a variable because you will want a unique name for your state.
IMO this syntax is much easier to understand than the Object.assign
You can use Object.assign() function:
var state = {
accessToken: null,
isLoggedIn: false,
};
var user = {
name: 'some name',
email: 'some email'
};
var newState = Object.assign({}, state, {user});
console.log(newState);
First I see that your state is actually an array, but I think you would need an object right?
So it would be:
const initialState = {
accessToken: null,
isLoggedIn: false,
}
(requires Babel) So with spread operator you can:
return {
...initialState,
user: {
name: '...',
surname: '...'
}
};
Or if you do not transpile via Babel alike:
return Object.assign({}, initialState, {
user: {
name: '...',
surname: '...'
}
});
Using ES6 spread syntax
...
case 'ACTION_TYPE_A': {
return { ...state, action.key: action.value };
}
...
This will return the merged state by updating the 'key' if it exists in the original state.
Everything according to new Es6 format : )
A total addToDo reducer function where the data is appended to the previous state. And you get the output of a new state data : )
export const addToDo = (state, action) => {
const { name, email, phone, image,key } = action;
var data = [...state.data];
var newData = {
name: name, email: email, phone: phone, image: image,key:key
}
data.push(newData)
return(
state.merge({
data : data
})
)};
Happy coding.

Categories