Redux initial state in reducer not accepting new values - reactjs

I'm running into a strange problem where the initial state in one of my reducers is not accepting new values. I've been able to add new values to this initial state easily, but for some reason now new initial state entries come back as undefined when I mapStateToProps.
//REDUCER
const initialState = {
(...cutting out a bunch of state here),
collectionSearchResults: {
results: {},
loading: false,
loaded: false,
error: ''
},
collectionImage: {
image: '',
loading: false,
loaded: false,
error: '',
},
collectionBeingEdited: {
collectionId: '',
loading: false,
complete: false,
error: '',
test: '',
},
removeReport: {
loading: false,
}
}
//INDEX of Component
const mapStateToProps = state => ({
(...cutting out a bunch of state here)
collectionBeingEdited: state.research.collectionBeingEdited,
removeReport: state.research.removeReport,
userInfo: state.account.myAccount.info,
})
//IN COMPONENT
console.log(this.props)
//result -> removeReport: undefined

InitialState of a reducer is not a reducer. As Martin suggested, you need to post your actual reducer.
I'm willing to bet that in one of your reducer cases its not returning the rest of the state:
case 'something':
return {
someKey: action.value
}
instead of:
case 'something':
return {
...state,
someKey: action.value
}
and thats why the property you're expecting doesnt exist.

Related

Redux reducer: Add object to array when the array doesn't have the object data

I'm trying to store AJAX call returned data object to an array of reducer state of Redux.
I have some conditions to check if the fetched data already exists inside of the reducer.
Here are my problems:
The component to call AJAX call actions, it's a function from mapDispatchToProps, causes an infinite loop.
isProductLikedData state in reducer doesn't get updated properly.
Can you tell what I'm missing?
Here are my code:
isProductLikedActions.js - action to fetch isProductLiked data. response.data looks like {status: 200, isProductLiked: boolean}
export function fetchIsProductLiked(productId) {
return function (dispatch) {
axios
.get(`/ajax/app/is_product_liked/${productId}`)
.then((response) => {
dispatch({
type: 'FETCH_IS_PRODUCT_LIKED_SUCCESS',
payload: { ...response.data, productId },
});
})
.catch((err) => {
dispatch({
type: 'FETCH_IS_PRODUCT_LIKED_REJECTED',
payload: err,
});
});
};
}
isProductLikedReducer.js - I add action.payload object to isProductLikedData array when array.length === 0. After that, I want to check if action.payload object exists in isProductLikedData or not to prevent the duplication. If there is not duplication, I want to do like [...state.isProductLikedData, action.payload].
const initialState = {
isProductLikedData: [],
fetching: false,
fetched: false,
error: null,
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case 'FETCH_IS_PRODUCT_LIKED': {
return { ...state, fetching: true };
}
case 'FETCH_IS_PRODUCT_LIKED_SUCCESS': {
return {
...state,
fetching: false,
fetched: true,
isProductLikedData:
state.isProductLikedData.length === 0
? [action.payload]
: state.isProductLikedData.map((data) => {
if (data.productId === action.payload.productId) return;
if (data.productId !== action.payload.productId)
return action.payload ;
}),
};
}
case 'FETCH_IS_PRODUCT_LIKED_REJECTED': {
return {
...state,
fetching: false,
error: action.payload,
};
}
}
return state;
}
Products.js - products is an array that fetched in componentWillMount. Once nextProps.products.fetched becomes true, I want to call fetchIsProductLiked to get isProductLiked` data. But this causes an infinite loop.
class Products extends React.Component {
...
componentWillMount() {
this.props.fetchProducts();
}
...
componentWillReceiveProps(nextProps) {
if (nextProps.products.fetched) {
nextProps.products.map((product) => {
this.props.fetchIsProductLiked(product.id);
}
}
render() {
...
}
}
export default Products;
Issue 1
The component to call AJAX call actions, it's a function from mapDispatchToProps, causes an infinite loop.
You are seeing infinite calls because of the condition you used in componentWillReceiveProps.
nextProps.products.fetched is always true after products (data) have been fetched. Also, note that componentWillReceiveProps will be called every time there is change in props. This caused infinite calls.
Solution 1
If you want to call fetchIsProductLiked after products data has been fetched, it is better to compare the old products data with the new one in componentWillReceiveProps as below:
componentWillReceiveProps(nextProps) {
if (nextProps.products !== this.props.products) {
nextProps.products.forEach((product) => {
this.props.fetchIsProductLiked(product.id);
});
}
}
Note: you should start using componentDidUpdate as componentWillReceiveProps is getting deprecated.
Issue 2
isProductLikedData state in reducer doesn't get updated properly.
It is not getting updated because you have used map. Map returns a new array (having elements returned from the callback) of the same length (but you expected to add a new element).
Solution 2
If you want to update the data only when it is not already present in the State, you can use some to check if the data exists. And, push the new data using spread syntax when it returned false:
case "FETCH_IS_PRODUCT_LIKED_SUCCESS": {
return {
...state,
fetching: false,
fetched: true,
isProductLikedData: state.isProductLikedData.some(
(d) => d.productId === action.payload.productId
)
? state.isProductLikedData
: [...state.isProductLikedData, action.payload],
};
}

Updating a value in an object in Redux reducer

I am looking to update value(s) in a nested object based on user input. So for instance, if the user chooses to update their country and street, only those values will be passed as the action.payload to the reducer and updated (the rest of the state will be kept the same). I provide my initial state and reducer:
My state:
const initialState = {
userData: [
{
firstName: "",
lastName: "",
country: "",
address: {
street: "",
houseNumber: "",
postalCode: "",
}
}
],
error: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_USER_DATA:
return { ...state, userData: action.payload };
case UPDATE_USER_DATA:
return {
...state,
userData: [{
...state.userData,
...action.payload,
}]
};
default:
return state;
}
};
Any help would be great, thank you!!
It doesn't appear that you need the array wrapping the object. If that is true, remove it for simplicity. Then userData becomes a plain object, and your update becomes:
return {
...state,
userData: { // <- no wrapping array, just update the object
...state.userData,
...action.payload,
}
};
Since there is an array, you would need to destructure the object at the correct index.
return {
...state,
userData: [{
...state.userData[0], // <- destructure the object, not the array
...action.payload,
}]
};
If you do need the array, and there will be more than one object, you will also need to pass an identifier in the action payload in order to know which index to update, but with the current information given this is the most complete answer possible.

Getting rid of console logs in redux wrapper with Next.js

Hey guys I just downloaded some source file from internet which uses redux thunk with Next.js in order to see how things work and then after running it with npm run dev the project runs successfully but then in console I keep getting this logs:
1. WrappedApp.getInitialProps wrapper got the store with state {
loginAndSignupReducer: [],
getCategoryReducer: { loading: false, err: false, data: [] },
MyStoryReducer: { loading: false, err: false, data: [] },
reportStoryReducer: { loading: false, err: false, data: [] },
getAllDetails: []
}
3. WrappedApp.getInitialProps has store state {
loginAndSignupReducer: [],
getCategoryReducer: { loading: false, err: false, data: [] },
MyStoryReducer: { loading: false, err: false, data: [] },
reportStoryReducer: { loading: false, err: false, data: [] },
getAllDetails: []
}
4. WrappedApp.render created new store with initialState {
loginAndSignupReducer: [],
getCategoryReducer: { loading: false, err: false, data: [] },
MyStoryReducer: { loading: false, err: false, data: [] },
reportStoryReducer: { loading: false, err: false, data: [] },
getAllDetails: []
}
These things get rendered first of all and so because of this. I also see them in page source as objects
I have shared an image below please refer to it, I rendered my id in <p> tag but then here I am seeing it as object.
Does anyone from where to get rid of them permanently?
This is most likely because you copy the code sample from the docs which sets debug to true when you create a wrapper:
export const wrapper = createWrapper<State>(makeStore, {debug: true});
Remove the debug property or set it to false to fix the issue
export const wrapper = createWrapper<State>(makeStore);
Live Demo
There are two reasons for this that I can think of, either the Debug Property is set to false during the wrapper creation. To Fix that, do this
// From This
const wrapper = createWrapper<RootState>(initializeStore, { debug: true })
// To This
const wrapper = createWrapper<RootState>(initializeStore, { debug: false })
// Or Rather
const wrapper = createWrapper<RootState>(initializeStore)
Or you might have some middleware like "logger" in your State. So if that's the case get rid of any middleware you don't need. Logger can be replaced with redux-devtools-extension

Redux single state update

I receive the data for all lamps in one request.
(I can, however, receive each lamp data from the server individually).
The data I receive from the server looks like this.:
[
{
"id": "1",
"state": 'ON'
},
{
"id": "2",
"state": 'OFF'
},
{
...
},
...
]
In my app, this data will be stored in a single redux state. I use FlatList to render this data along with a simple Switch so that the user can turn each lamp ON or OFF.
When the user changes a lamp's state, a LOADING action will be dispatched and the whole lamps will show a spinner.
When the updated data is received from the server, a SUCCESS action will be dispatched, the redux state will be updated, the spinner will disappear and finally, the updated data will be shown.
Problem 1: When the user interacts with only one lamp, I don't want all lamps to go into LOADING state!
Problem 2: I never know how many lamps I will receive in my requests.
Desired Behaviour: Only the lamp, with which the user has interacted, must show a spinner and goes thrown the update process.
I need help with handling the redux states.
I hope this information helps. Below you can find my Rudcers for lightingInitReducer (getting data for all lamps) and lightingChangeStateReducer (changing the state of a lamp). isLoading is used for showing the spinner. Additionally, when the change state process was successful (LIGHTING_CHANGE_STATUS_SUCCESS is dispatched), the new data will be requsted from the server with an init request:
export const lightingInitReducer = (state = {
isLoading: false,
errMess: null,
info: {},
}, action) => {
switch (action.type) {
case ActionTypes.LIGHTING_INIT_SUCCESS:
return {
...state, isLoading: false, errMess: null, info: action.payload,
};
case ActionTypes.LIGHTING_INIT_FAILED:
return {
...state, isLoading: false, errMess: action.payload, info: {},
};
case ActionTypes.LIGHTING_INIT_LOADING:
return {
...state, isLoading: true, errMess: null, info: {},
};
default:
return state;
}
};
export const lightingChangeStateReducer = (state = {
isLoading: false,
errMess: null,
info: {},
}, action) => {
switch (action.type) {
case ActionTypes.LIGHTING_CHANGE_STATUS_SUCCESS:
return {
...state, isLoading: false, errMess: null, info: action.payload,
};
case ActionTypes.LIGHTING_CHANGE_STATUS_FAILED:
return {
...state, isLoading: false, errMess: action.payload, info: {},
};
case ActionTypes.LIGHTING_CHANGE_STATUS_LOADING:
return {
...state, isLoading: true, errMess: null, info: {},
};
default:
return state;
}
};
Check out immutability-helper: https://github.com/kolodny/immutability-helper
It's amazing. Reducers can get real messy with deeply nested states, and this lib helps tidy things up a lot.
For problem 1, here is an idea of how you could update a single lamp, and display a loading state for that one item:
You can use the index of the pressed list item from your FlatList and pass that along to the LOADING action that dispatches your server request. You can then use this index in your reducer to update that specific lamp in your state. Using the immutability-helper library, the reducer might look like this:
import update from 'immutability-helper';
case UPDATE_LAMP_REQUEST:
let newState = update(state, {
lamps: {
[action.index]: {
loading: { $set: true }, // turns on loading state for lamp at index
}
}
})
return newState;
Once the web request has complete, and your SUCCESS action has dispatched, you can then pass that same index from your FlatList to your reducer, and update the lamp in your state with the updated lamp from the server:
import update from 'immutability-helper';
case UPDATE_LAMP_SUCCESS:
let newState = update(state, {
lamps: {
[action.index]: {
loading: { $set: false }, // turn off loading
$set: action.lamp // set the new lamp state
}
}
})
return newState;
You'd then connect the "loading" field to the component responsible for a single list view item in your FlatList and use that value to hide / show the loading state.
As for problem 2, I'm not sure I understand the question.

How to return state of Redux reducers

For redux reducer step :
What if I only want to change one single property of initial_state. For example:
const INITIAL_STATE = {
signInInfo: {
signin: false,
name: "",
email: "",
...
},
changePassword: {
status: false,
...
}
...
};
Here I only want to set signInInfo.signin as true, currently , the only way I know is to input a complete "signInInfo" like :
case SIGNIN_USER:
return { ...state, signInInfo: action.payload.data };
action.payload.data is like:
{
signin: true,
name: "Qing",
email : ...
}
And another question is what if I want to set signInInfo.signin as false and meanwhile also need to change changePassword.status from false to true.
What should I do? Can anyone give me a hint?
Fairly simple (both questions):
case SIGNIN_USER: return {
...state,
signInInfo: {
...state.signInInfo,
signin: action.payload.signin
},
changePassword: {
...state.changePassword,
status: true
}
};

Resources