Can't receive Async response in componentDidMount with Redux - reactjs

lately i'm facing a tough issue with React/Redux (Thunk): i've created my Store with Action and Reducer properly, in my Component i trigger the Async function in componentDidMount method in order to update the state, But the State doesn't seems to be changing, although it does changed in componentDidUpdate and mapStateToProps functions ! Why ? Here is my code :
export const getAllInterventions = () => {
return dispatch => {
dispatch(getAllDataStart());
axios.get('/interventions.json')
.then(res => {
dispatch(getAllDataSuccess(res.data));
})
.catch(err => {
dispatch(getAllDataFail(err));
});
};
My reducer :
case actionTypes.GET_ALL_INTERVENTIONS_SUCCESS:
return {
...state,
interventions: interventions: action.interventions
};
My Component:
componentDidMount() {
this.props.getAllInterventions();
console.log('DidMount: ', this.props.inter); /*Empty Array Or Undefined */
}
const mapStateToProps = state => {
console.log('mapStateToProps', state);
return {
inter: state.interventions,
error: state.error
};

Your action and state changes are both asynchronous, doing console.log right after will always return before those changes are actually completed.
Your state is being updated, which is why it works when you console.log in the mapStateToProps.
I suggest setting up redux-devtools, you'll be able to easily track your actions/state.

I hope the code can be usefully for you
componentWillReceiveProps(nextProps){
console.log(nextProps.inter)
}

Related

React-Redux: how to set the state?

I am trying to understand someone else their code but have difficulty understand the interaction between Redux and React.
On a React page, I invoke a Redux action called getSubscriptionPlan. Inside that Redux action, I see it is able to load the correct data (point 1 below). This uses a reducer, in which I can again confirm the correct data is there (point 2 below).
Then the logic returns to the React page (point 3 below). I now would expect to be able to find somewhere in the Redux store the previously mentioned data. However, I can't find that data listed anywhere... not in this.state (where I would expect it), nor in this.props. Did the reducer perhaps not update the store state...?
What am I doing wrong and how can I get the data to point 3 below?
React page:
import { connect } from "react-redux";
import { getSubscriptionPlan } from "../../../appRedux/actions/planAction";
async componentDidMount() {
let { planId } = this.state;
await this.props.getSubscriptionPlan(planId);
// 3. I can't find the data anywhere here: not inside this.state and not inside this.props.
this.setState({plan: this.state.plan});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.payment.paymentData !== this.props.payment.paymentData) {
this.setState({
checkout: this.props.payment.paymentData,
plan: this.props.payment.paymentData.plan,
});
}
}
const mapStateToProps = (state) => {
return {
plan: state.plan,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ getSubscriptionPlan }, dispatch
);
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Checkout)
);
Redux action:
export const getSubscriptionPlan = (id) => {
let token = getAuthToken();
return (dispatch) => {
axios
.get(`${url}/getSubscriptionPlan/${id}`, {
headers: { Authorization: `${token}` },
})
.then((res) => {
if (res.status === 200) {
// 1. From console.log(res.data) I know res.data correctly now contains the data
return dispatch({
type: GET_PLAN_SUCCESS,
payload: res.data,
});
})
};
};
Reducer:
export default function planReducer(state = initial_state, action) {
switch (action.type) {
case GET_PLAN_SUCCESS:
// 2. I know action.payload, at this point contains the correct data.
return { ...state, plan: action.payload };
default:
return state;
}
}
You are getting tripped up on how Redux works.
Redux does not use react component state. It manages state separately, and passes that state to components as props. When you call getSubscriptionPlan, you asynchronously dispatch an event to Redux, which handles the event and updates store state in the reducer. This state is the passed to the connected components mapStateToProps function, mapped to props, and then passed as props to your component. Passing new props triggers a componentDidUpdate and a rerender of the component.
A few key things here.
Redux does not interact with component state unless you explicitly set state with props passed from Redux.
Redux is asynchronous. That means that when you make a change to state via dispatch, the change is not immediately available in the component, but only available when new props are passed. It's event driven, not data binding. As a result, in your code you woun't see the plan prop in componentDidMount because at the time componentDidMount the call to getSubscriptionPlan hasn't happened.
You should see the prop populated in this.props in componentDidUpdate and in render before the didUpdate.
When working with react, it's best to think of components as basically functions of props with some extra lifecycle methods attached.

Redux state rerender with useSelector - how to achieve it?

I have a problem with redux re-render. Or rather lack of it, because using useState, everything works perfectly. My question is - how should I use the selector, so once the data is fetched, the component is re-rendered. Also, with redux approach sometimes the object X returns an error of undefined - because the assigned data is not yet fetched. How to overcome this? Should I use useSelector in the useState? I rather use redux than the local state in this case.], but redux gives me this issue that I am not sure how to overcome.
So I am fetching my data in the useEffect hook. This should also update the redux' state (which it does).
useEffect(() => {
FetchService.fetch()
.then((res) => dispatch(fetch(res)))
.catch((err) => console.log(err));
}, []);
Then I am grabbing the state using a right selector:
const data = useSelector(selectData)
At the very end I am assigning this to the object (I cannot skip this, cuz I need it), but sometimes I get an undefined error already here, cuz the data is an empty array):
const DATA_TAB_DATASOURCES = {
dataX: [data],
};
In the component itself I check conditionally if the array isn't undefined, but it seems the problem comes from the selector as with the useState it works perfectly as the re-render is automatically triggered with the change.
{data && DATA_TAB_DATASOURCES[dataX].map((c: any, i: number) =>
.....
})}
EDIT:
Reducer:
const INITIAL_STATE: any = {
data: []
};
export const reducer = (state: any = INITIAL_STATE, action: any) => {
switch (action.type) {
case FETCH: {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
};
export default reducer;
selector:
const globalSelector = (state: any) => state
export const selectData = createSelector(globalSelector, (data) => {
return data
});

Redux state has changed, why doesn't it trigger a re-render? (Redux-saga)

I'm using react + redux + redux saga
I'm facing the issue that when I'm rendering the page (GET call)
The calls should be like:
constructor
render()
componentDidMount
render()
But I'm just reaching up to componentDidMount, mapDispatchToProps is dispatching the action, API call is working by which getting the response from the server and the data is updated into the state.
BUT somewhere it gets lost and my component is not even re rendering.
Up to the reducer, I'm getting the data where I'm returning action.items.
itemReducer.js
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_ALL_ITEMS_SUCCESS:
console.log("itemReducer-----", action.items); //getting the data over here
return action.items;
default:
return state;
}
};
itemPage.js (component)
class ItemsPage extends React.Component {
componentDidMount() {
this.props.loadItems();
}
render() {
const { items } = this.props; // not even it renders, so not getting data
...
return (<div>...</div>);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadItems: () => dispatch(loadAllItemsAction()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ItemsPage);
Please give some suggestions, Thanks in advance :D
There isn't issue in the code that you posted. To make sure I pretty much copy pasted the code you shared, filled in the missing parts and it works just fine:
https://codesandbox.io/s/3tuxv?file=/src/index.js
My guess what might be wrong is that your items are not stored in state.items but under some different path or you might be missing the react-redux Provider, but it is impossible to say for sure without seeing more of your code.
You need to understand that the calls of render/componentDidMount are not so linear as it could be expected. componentDidMount fires when all children were mount. And it doesn't means that render() was finished already.
Read here for more info:
https://github.com/facebook/react/issues/2659

Setting redux state doesn't work after a hard refresh

I am setting my redux state through a value I have in localStorage. This works fine when I navigate into my page. However, when I do a hard refresh the state is never set, despite the value in localStorage being passed down.
This is what my code looks like:
class SomeComponent {
componentWillMount() {
if (typeof localStorage !== 'undefined') {
console.log('I get to here...', localStorage.getItem('someValue')) // this comes in as expected always
this.props.setMyReduxState(localStorage.getItem('someValue'))
}
}
render () {
// will have the value of the localStorage item someValue when navigated into the page
// will be an empty string if I do a hard refresh
console.log('this.props.myReduxState', this.props.myReduxState)
return (
<div>
Stuff...
</div>
)
}
}
const mapStateToProps = (state) => {
return {
myReduxState: state.something.myReduxState || ''
}
}
const mapDispatchToProps = (dispatch) => {
return {
setMyReduxState (someValue) {
dispatch(setMyReduxState(someValue))
}
}
}
Any ideas?
Edit: just a small addition to simplify the problem: I also tried it sending a string directly to setMyReduxState function, without the localStorage, the state still isn't being set. So basically something like this:
componentWillMount() {
this.props.setMyReduxState('some string!')
}
From my understanding every time the redux state is set, the component should re-draw, which isn't happening when there is a hard refresh. Are there any reasons for this or something being done incorrectly?
Edit2: Including my action creator and reducer in case needed:
const SET_MY_REDUX_STRING = 'admin/users/SET_MY_REDUX_STRING'
const defaultState = {
myReduxState: ''
}
export function setMyReduxState (value) {
return {
type: SET_MY_REDUX_STRING,
myReduxState: value
}
}
export default function reducer (state = defaultState, action = {}) {
switch (action.type) {
case SET_MY_REDUX_STRING:
return Object.assign({}, state, { myReduxState: action.myReduxState })
default:
return state
}
}
Some of the checklist you need to follow while using redux -
Are you dispatching an action creator that returns an object with 'type' and data? Here 'type' is mandatory.
Does your reducer return the state with the updated data that it received?
Make sure you do not mutate the state in reducer. Always use {...state, someKey: someValue}

componentWillReceiveProps not called despite returning new state object

I have the following mapDispatchToProps
const mapDispatchToProps = dispatch => ({
surveysRequest: () => dispatch(DashboardActions.surveysRequest()),
surveysFilter: () => dispatch(DashboardActions.surveysFilter())
});
and in my componentDidMount I'm calling the method
componentDidMount() {
if (this.animation) {
this.animation.play();
}
this.props.surveysRequest()
}
The surveysSuccess reducer does get called, an even though I am returning a new state, componentWillReceiveProps is never called
const surveysSuccess = createAsyncSuccess(ASYNC_GET, (state, action) => {
return state.merge({
surveys: action.data
})
}
)
Using the latest ignite react native boilerplate https://github.com/infinitered/ignite
https://levelup.gitconnected.com/componentdidmakesense-react-lifecycle-explanation-393dcb19e459
componentWillReceiveProps is only called when the props that the component receives changes. If the state changes, componentWillReceiveProps will not be called. Try using componentWillUpdate or componentShouldUpdate.
You are making shallow copy of your state object and nested values are still being passed by reference.
From docs here
mapStateToProps function will also be re-invoked whenever the connected component receives new props as determined by shallow equality comparisons.
Solution :
const surveysSuccess = createAsyncSuccess(ASYNC_GET, (state, action) => {
let newState= JSON.parse(JSON.stringify(state)); //add it before using state.
//rest code
})

Resources