Redux state coming out to be undefined - reactjs

I am having tough time figuring out why my redux state is coming out to be undefined on a later stage.
so I have an action which looks like this
export const coinUpdateState = (booleanValue) => {
console.log(typeof booleanValue) //This logs Boolean
return function (dispatch) {
dispatch({
type: COIN_UPDATE_STATE,
payload: booleanValue
})
}
}
and a reducer which looks like this
const initialState = {
DataFetching: true,
DataSucess: [],
DateError: [],
DateError: false,
DataSort: true,
DataUpdate: true
}
export default function(state = initialState, action) {
switch (action.type) {
case EXCHANGE_CURRENCY_FETCHING:
return {
DataFetching: true,
DataSort: true
}
case EXCHANGE_CURRENCY_FETCH_SUCCESS:
return {
...state,
DataSucess: action.payload,
DataFetching: false,
DataSort: false
}
case EXCHANGE_CURRENCY_FETCH_ERROR:
return {
...state,
DateError: action.payload,
DataFetching: true,
DateError: true,
DataSort: true
}
case COIN_UPDATE_STATE:
console.log(action.payload) //This logs the boolean value I am sending
return {
DataUpdate: action.payload
}
default:
return state
}
}
Later I am using it like this in my app
render () {
console.log(this.props.cryptoUpdateState)
if (this.props.cryptoUpdateState) {
console.log("Inside if render")
displayData = async () => {
this.coinURL = await AsyncStorage.getItem("CryptoCurrencySelected").catch((error) => {
console.log(error)
})
if (this.coinURL != "undefined" && this.coinURL != null) {
console.log("Inside Async", this.coinURL)
this.props.exchangeToDisplay(this.coinURL)
}
if (this.coinURL == null || this.coinURL == "undefined") {
console.log("Null", this.coinURL)
this.coinURL = "BTC"
}
}
displayData()
this.props.coinUpdateState(false)
}
In the above snippet notice the initial console.log, it logs true correctly the first time, there after it logs undefined in console when I am actually passing false (this.props.coinUpdateState(false)).
Also Notice the logs in my code, they are logging the value I am sending correctly everywhere besides in the later stage where it is logging undefined (in console.log).
Question: What could I be doing wrong here?

A reducer will return a new version of the state of your app. If you don't want to lose your previous state you will need to make sure you always return it with any additions/modifications you need.
Whatever is returned from the switch case that is triggered is going to be the next version of state, which for COIN_UPDATE_STATE is going to just have DataUpdate in.
Make sure all you are doing ...state for all your redux reducer actions in order to make sure you keep state.
e.g.
case COIN_UPDATE_STATE:
return {
...state,
DataUpdate: action.payload
}

Related

React redux two states conflict

I have two reducers in my redux mention below -
case actionTypes.SENT_REQUEST_FAIL:
return {
showAlert: true,
}
case actionTypes.GET_STUDENT_PROFILE_BY_PROFILEID_SUCCESS:
return {
showAlert: false,
}
and i need showAlert:true in some case but last reducer call is GET_STUDENT_PROFILE_BY_PROFILEID_SUCCESS , so always showAlert become false. how do i run SENT_REQUEST_FAIL in end to get true value.
const cardData = async (val1, val2, val3) => {
setshowsentRequestModal(false);
handleModal(false);
//this is true one
await props.sentRequest(val1, val2, val3);
};
const handleModal = (val) => {
props.handleSentRequestModal(val);
//this is false one
props.getStudentProfileByProfileId(studentId);
};
In your approach, state is not update. It always return the initial value.
you need to pass the current state to reducer and update it as below.
case actionTypes.SENT_REQUEST_FAIL:
return {
...state, showAlert: true
};
case actionTypes.GET_STUDENT_PROFILE_BY_PROFILEID_SUCCESS:
return {
...state, showAlert: false
};

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],
};
}

React-Redux , issues reading an object fetched through redux thunk

I have a redux action / reducer that looks like the following.
Action:
export function loadServerInfo() {
return (dispatch) => axios.get(`${config.SERVER}/redis/server/info`).then(res => {
if (res.status == 200) {
dispatch(fetchServerInfo(res.data))
}
}).catch(err => {
})
}
export function fetchServerInfo(payload) {
return {
type: GET_SERVER_INFO,
payload
}
}
Reducer:
const defaultState = {
decodedRedisKey: {},
keyDecoded: false,
serverInfo: {}
}
const redisReducer = (state = defaultState, action: Action) => {
switch (action.type) {
case GET_REDIS_KEY_INFO: {
return {
...state,
decodedRedisKey: action.payload
}
}
case REDIS_KEY_DECODED: {
return {
...state,
keyDecoded: action.payload
}
}
case GET_SERVER_INFO: {
console.log(action.payload) //this is fired and logs the proper data, which is an object
return {
...state,
serverInfo: action.payload
}
}
default:
return {
...state
};
}
}
export default redisReducer;
Then I have a component connected and mapped to redux. Those are the connection parameters
const mapStateToProps = (state) => state;
function mapDispatchToProps(dispatch) {
return {
loadServerInfo: async () => {
dispatch(loadServerInfo());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UsersContainer);
And after that, I try to call the fetch, and get the data.
Problem is that the format of the object is as follows:
serverInfo: {
Server : {
uptime_in_days: "100",
version: "1.0.0"
}
}
My prop is firing on useEffect
React.useEffect(() => {
getUsersToken();
props.loadServerInfo();
console.log(process.env.REACT_APP_ENV)
}, []);
If i put it in a useEffect, first it logs undefined and afterward it loads
React.useEffect(() => {
console.log("server info")
console.log(props.redisReducer.serverInfo)
console.log(props.redisReducer.serverInfo.Server)
// console.log(props.redisReducer.serverInfo.Server.uptime_in_days) , if i uncomment this it crashes
}, [props.redisReducer.serverInfo])
So im having issues rendering the uptime_in_days value
I have tried doing this
{props.redisReducer.serverInfo != undefined && !displayServerInfo != undefined ?
<div className="basic-server-info-data">
<p><img src={redisLogo} /></p>
{/* <p>Connected Clients: <i>{serverInfo.Clients.connected_clients} </i></p> */}
{/* <p>Memory usage: <Progress type="circle" percent={memoryUsageStats} width={50} /> </p> */}
<p>Tokens (displayed): <i>{usersToken.length}</i></p>
<p>Uptime: <i>{props.redisReducer.serverInfo.Server.uptime_in_days} days</i></p>
</div>
:
null
}
It keeps crashing in the Uptime line, even tho im doing a check if its not undefined
Cannot read property 'uptime_in_days' of undefined
I tried changing the render condition to
props.redisReducer.serverInfo != undefined && !displayServerInfo != undefined && props.redisReducer.serverInfo.Server.uptime_in_days != undefined
But nothing changes.
How can I render that value?
EDIT: I have noticed this error
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
in my useEffect
Issue
The issue is that all your null checks start with the always defined state, props.redisReducer.serverInfo
const defaultState = {
decodedRedisKey: {},
keyDecoded: false,
serverInfo: {} // <-- defined!
}
state.serverInfo is always a defined object, so console.log(props.redisReducer.serverInfo) and console.log(props.redisReducer.serverInfo.Server) will always log, and the condition props.redisReducer.serverInfo != undefined will always be true.
You neglect to do a null check on props.redisReducer.serverInfo.Server before accessing the uptime value
props.redisReducer.serverInfo.Server.uptime_in_days
I'm guessing your UI is blowing up on the initial render before state is populated.
Solutions
Use Optional Chaining to handle the null check on Server being possibly undefined still.
props.redisReducer.serverInfo.Server?.uptime_in_days
Use conventional null checks
props.redisReducer.serverInfo.Server &&
props.redisReducer.serverInfo.Server.uptime_in_days

React Native ShouldComponentUpdate not firing between Redux Props Changes

In optimizing my component for performance, I noticed that sequential shouldComponentUpdate calls were seemingly missing the prop change from my redux store. An example being:
In props:
uiBehavior: {
shouldShow: false,
}
Redux Action:
fireActionToShow()
Redux Reducer:
case ActionType.UPDATE_UI_BEHAVIOR: return {...state, shouldShow: true}
Then I'm seeing:
shouldComponentUpdate(nextProps) {
// Would expect to at some point see:
this.props.shouldShow === false
nextProps === true
// Instead, only seeing
this.props.shouldShow === false
nextProps === false
// then
this.props.shouldShow === true
nextProps === true
}
** Note that this clearly isn't the code, just an example
It seems to me that a prop change isn't causing a rerender attempt, or am I missing something?
Thanks.
Expanding for clarity, here is some of the real code:
*** the event in the Action updates the uiBehavior prop on the redux store.
componentDidUpdate(prevProps, prevState) {
const { uiBehavior } = this.props;
if (!_.isEqual(uiBehavior.lockAttributes, prevProps.uiBehavior.lockAttributes)) {
console.log('Lock has changed.'); // This never gets called
}
}
const mapStateToProps = function(state){
return {
uiBehavior: state.uiBehavior,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SlotMachine)
*** UPDATE
JS Call:
this.props.setGeneralUiKeyValue('lockAttributesInCart', !lockAttributes);
Action:
export const setGeneralUiKeyValue = (key, value) => { return { type: GENERAL_UI_UPDATE, key, value }}
Reducer:
export const uiBehavior = (state = {}, action) => {
let newUiState = {...defaultState, ...state}
switch (action.type) {
case uiActionTypes.GENERAL_UI_UPDATE:
newUiState.general = newUiState.general || {};
newUiState.general[action.key] = action.value;
return newUiState
default:
return newUiState;
}
return newUiState

Redux overwrites model with previous state

I am currently making a sample project in AngularJs combined with Redux.
I am struggling to get the mappings from the reducer working.
I have a simple input where users can set a new name together with a drop down to select a 'company'.
<input type="text" ng-model="$ctrl.single.object.name">
<select ng-change="$ctrl.getProperties()"
ng-options="option.description as option.description for option in $ctrl.list.all"
ng-model="$ctrl.single.object.company">
When the user changes the company, new properties need to be fetched in order for the user to set these properties.
function FooController($ngRedux, FooActions, BarActions) {
this.$onInit = function () {
this.unsubscribeCompanies = $ngRedux.connect(this.mapStateToThis, BarActions)(this);
this.fetchCompanyList();
};
this.$onDestroy = function () {
this.unsubscribeCompanies();
};
this.fetchCompanyList = function () {
this.fetchCompanies().payload.then((response) => {
this.fetchCompaniesSuccess(response.data);
}, (error) => {
this.fetchCompaniesError(error.data);
});
};
this.getProperties = function () {
this.fetchCompanyProperties(this.single.object.company).payload.then((response) => {
this.fetchCompanyPropertiesSuccess(response.data);
}, (error) => {
this.fetchCompanyPropertiesError(error.data);
});
};
this.mapStateToThis = function (state) {
return {
list: state.bar.list,
single: state.bar.single
};
};
}
module.exports = {
template: require('./index.html'),
controller: ['$ngRedux', 'FooActions', 'BarActions', FooController]
}
The problem I get is that the name and the selected company are overwritten with empty values when the fetch for properties is successful. I get why the values are overwritten with empty values and I have found a way to get it working.
export const GET_COMPANIES = 'GET_COMPANIES';
export const GET_COMPANIES_SUCCESS = 'GET_COMPANIES_SUCCESS';
export const GET_COMPANIES_ERROR = 'GET_COMPANIES_ERROR';
export const GET_COMPANIES_PROPERTIES = 'GET_COMPANIES_PROPERTIES';
export const GET_COMPANIES_PROPERTIES_SUCCESS = 'GET_COMPANIES_PROPERTIES_SUCCESS';
export const GET_COMPANIES_PROPERTIES_ERROR = 'GET_COMPANIES_PROPERTIES_ERROR';
export default function BarActions($http) {
function fetchCompanies() {
return {
type: GET_COMPANIES,
payload: $http.get('api/companies')
};
}
function fetchCompaniesSuccess(companies) {
return {
type: GET_COMPANIES_SUCCESS,
payload: companies
};
}
function fetchCompaniesError(error) {
return {
type: GET_COMPANIES_ERROR,
payload: error
};
}
function fetchCompanyProperties(company) {
return {
type: GET_COMPANIES_PROPERTIES,
payload: $http.get(`api/company/${company}/properties`)
};
}
function fetchCompanyPropertiesSuccess(properties) {
return {
type: GET_COMPANIES_PROPERTIES_SUCCESS,
payload: properties
};
}
function fetchCompanyPropertiesError(error) {
return {
type: GET_COMPANIES_PROPERTIES_ERROR,
payload: error
};
}
return {
fetchCompanies,
fetchCompaniesSuccess,
fetchCompaniesError,
fetchCompanyProperties,
fetchCompanyPropertiesSuccess,
fetchCompanyPropertiesError
}
}
The way I overwrite the values in the reducer is as follows:
import { GET_COMPANIES, GET_COMPANIES_SUCCESS, GET_COMPANIES_ERROR, GET_COMPANIES_PROPERTIES, GET_COMPANIES_PROPERTIES_ERROR, GET_COMPANIES_PROPERTIES_SUCCESS } from "../actions/bar.actions";
const all = [];
const initialState = {
list: {
all,
filtered: all,
error: null,
loading: false
},
single: {
object: {},
error: null,
loading: false
}
};
export function BarReducer(state = initialState, action) {
switch (action.type) {
case GET_COMPANIES:
return { ...state, list: { all: [], filtered: [], error: null, loading: true } };
case GET_COMPANIES_SUCCESS:
return { ...state, list: { all: action.payload, filtered: action.payload, error: null, loading: false } };
case GET_COMPANIES_ERROR:
return { ...state, list: { all: [], filtered: [], error: action.payload.innerException, loading: false } };
case GET_COMPANIES_PROPERTIES:
return { ...state, single: { ...state.single, object: { ...state.single.object }, error: null, loading: true } };
case GET_COMPANIES_PROPERTIES_SUCCESS:
return { ...state, single: { ...state.single, object: { ...state.single.object, payloadValues: action.payload }, error: null, loading: false } };
case GET_COMPANIES_PROPERTIES_ERROR:
return { ...state, single: { object: null, error: action.payload.innerException, loading: false } };
default:
return state;
}
}
The way I now use the spread operator in order to overwrite the old state feels dirty. I was wondering if there are any rules or guidelines to handle this issue. So far I have searched a while on internet and in specific the Redux website but I did not come cross any other solutions.
The breakage is likely due to the structure of the reducer. It is concerned with too many different parts of state and has to operate on deep nested objects, making it easy to accidentally mutate state. The guidelines for reducer structure say that splitting reducer state into normalized slices is the best way to go.
Try splitting your one reducer into multiple smaller reducers. For example:
export const all = (initialAll = [], { type, companies }) => {
switch(type) {
case GET_COMPANIES_SUCCESS: return companies;
default: return initialAll;
}
}
export const error = (initialError = '', { type, error }) => {
switch(type) {
case GET_COMPANIES_ERROR: return error;
default: return initialError;
}
}
export const isFetching = (isFetching = false, { type }) => {
switch(type) {
case GET_COMPANIES: return true;
case GET_COMPANIES_SUCCESS: return false;
case GET_COMPANIES_ERROR: return false;
default: return isFetching;
}
}
Then, compose them into one reducer:
import { combineReducers } from 'redux';
export list = combineReducers({
all,
error,
isFetching
});
// ...
export rootReducer = combineReducers({
list,
single,
// ...
})
This way, each reducer is concerned with only one thing or set of things, and its reduction handlers can do simple operations on single-level state instead of complex operations on deep nested state.
Also, in your list substate, it looks like you are storing the same type of collection resources in both all and filtered with potential overlap. This leads to multiple sources of truth for the same data, which opens the door to data inconsistency. Instead, keep an array of filteredIds:
export const filteredIds = (initialIds = [], { type, filteredIds }) => {
switch(type) {
case SET_FILTERED_IDS: return filteredIds;
default: return initialIds;
}
}
Then, use a selector that filters all by the filteredIds to get your filtered items.
One option is to use Immutable, which would change your reducers to:
case GET_COMPANIES:
return state.setIn(['list', 'loading'], true);
// etc
See Using Immutable.JS with Redux for more information about this approach.
Another option is to use Lodash, as shown in this Issue, you can define the following function to make it similar to the immutable one:
import {clone, setWith, curry} from 'lodash/fp';
export const setIn = curry((path, value, obj) =>
setWith(clone, path, value, clone(obj)),
);
Then you can use setIn as follow:
case GET_COMPANIES:
return setIn(['list', 'loading'], true, state);
// etc
The Lodash approach is just working with plain object, so it might be easier to understand than Immutable.

Resources