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.
Related
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
}
}
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;
}
};
I'm trying some app in react redux and i have a problem with updating (push, remove, update) the nested array in state.
I have some object called service like this:
{
name: 'xzy',
properties: [
{ id: 1, sName: 'xxx'},
{ id: 2, sName: 'zzz'},
]
}
Whatever I did (in case of adding property to collection) in the reducer with the properties collection generate problem that all properties got same values as the last I had recently added -> Added property object is in service properties collection but the action replace all values in all properties in this collection.
My reducer:
export function service(state = {}, action) {
switch (action.type) {
case 'ADD_NEW_PROPERTY':
console.log(action.property) // correct new property
const service = {
...state, properties: [
...state.properties, action.property
]
}
console.log(service); // new property is pushed in collection but all properties get same values
return service
default:
return state;
}
}
I have tried some solution with immutability-helper library and it generate the same problem:
export function service(state = {}, action) {
case 'ADD_NEW_PROPERTY':
return update(state, {properties: {$push: [action.property]}})
default:
return state;
}
For example when I add new property { id: 1, sName: 'NEW'} to example above I will get this state:
{
name: 'xzy',
properties: [
{ id: 1, sName: 'NEW'},
{ id: 1, sName: 'NEW'},
{ id: 1, sName: 'NEW'}
]
}
Can someone help? :)
Make a copy of action.property as well. Whatever is dispatching this action, it could be reusing the same object.
export function service(state = {}, action) {
switch (action.type) {
case 'ADD_NEW_PROPERTY':
console.log(action.property) // correct new property
const service = {
...state,
properties: [
...state.properties,
{ ...action.property }
]
}
console.log(service); // new property is pushed in collection but all properties get same values
return service
default:
return state;
}
}
I'd recommend you to use Immutable data https://facebook.github.io/immutable-js/docs/#/List
import { fromJS, List } from 'immutable';
const initialState = fromJS({
propeties: List([{ id: 1, sName: 'xyz' }]
}
function reducer(state = initialState, action) {
case ADD_NEW_PROPERTY:
return state
.update('properties', list => list.push(action.property));
// ...
}
Your service reducer should probably look somewhat like this:
// Copy the state, because we're not allowed to overwrite the original argument
const service = { ...state };
service.properties.append(action.property)
return service
You should always copy the state before returning it.
export default function(state = {}, action) {
switch(action.type) {
case 'GET_DATA_RECEIVE_COMPLETE': {
const data = action.firebaseData;
const newState = Object.assign({}, state, {
data
});
return newState
}
default:
return state;
}
}
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:[];
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 }] });