Redux fails assigning integer value - reactjs

In CategoryFeed class, I have something like following:
class CategoryFeed extends Component {
...
componentDidMount() {
const {
params,
currentForum,
updateCurrentForum,
} = this.props;
alert(params.fid);
updateCurrentForum(params.fid);
alert(currentForum);
}
...
export default connect(
(state) => { return {
currentForum: state.app.currentForum,
...
(dispatch) => { return {
updateCurrentForum: (currentForum) => { dispatch(updateCurrentForum(currentForum)); },
...
This is what updateCurrentForum looks like:
export const updateCurrentForum = (currentForum) => {
alert("inside is " + currentForum);
return {
type: UPDATECURRENTFORUM,
payload: currentForum,
};
};
In Reducer, I have defined like:
case UPDATECURRENTFORUM:
return Object.assign({}, state, {
currentForum: action.payload,
});
Here is how it supposed to work from my expectation.
When the CategoryFeed is loaded, it alerts params.fid (let's say params.fid = 1). params.fid is actually additional string after my main url (e.g. if url was http://localhost/1, then params.fid is 1).
Then it stores the value of params.fid (=1) to currentForum via Redux
After I set currentForum by putting payload value to it, then I tried alert the value of currentForum in componentDidMount(). However, it does not show "1" but it shows "undefined". It looks like redux has failed putting params.fid to currentForum.
How can I fix this?

You will not be able to get the updated value of currentForum in componentDidMount(). This is because componentDidMount() only runs once the component is mounted.
Changes to props in componentDidMount() will cause the component to re-render. So the updated value will be accessible in the render() or componentDidUpdate() cycles.
You can move your alert or console.log to the render method and you should see the updated value

componentDidMount will be called after component is inserted into DOM tree and inside that you called updateCurrentForum(params.fid) which will update currentForum but this change will be caught in componentDidUpdate. for more details you can see lifecycle diagram of component http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
currently currentForum holds value from
const {
params,
currentForum,
updateCurrentForum,
} = this.props;
which might be undefined currently. try to assign some value in props and see if it changes from undefined to value you provided

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.

Initial State of Redux Store with JSON Data?

I am working on React app where the state is managed by redux. I am using actions.js file to fetch JSON data and store it directly in the store. The initial Store has just one key (data) in its obj with null as its value.
I use componentDidMount() Lifecycle to call the function which updates the store's data key with the JSON data I receive. However, whenever I load my app it gives an error because it finds the data value as null.
I get it. componentDidMount() executes after the app is loaded and the error doesn't let it execute. I tried using componentWillMount() but it also gives the same error. ( Which I use in JSX )
When I try to chanage the data's value from null to an empty obj it works for some level but after I use it's nested objects and arrays. I get error.
I wanna know what is the way around it. What should I set the vaue of inital State or should you use anyother lifecycle.
If your primary App component can't function properly unless the state has been loaded then I suggest moving the initialization logic up a level such that you only render your current component after the redux state has already been populated.
class version
class LoaderComponent extends React.Component {
componentDidMount() {
if ( ! this.props.isLoaded ) {
this.props.loadState();
}
}
render() {
if ( this.props.isLoaded ) {
return <YourCurrentComponent />;
} else {
return <Loading/>
}
}
}
export default connect(
state => ({
isLoaded: state.data === null,
}),
{loadState}
)(LoaderComponent);
Try something like this. The mapStateToProps subscribes to the store to see when the state is loaded and provides that info as an isLoaded prop. The loadState in mapDispatchToProps is whatever action creator your current componentDidMount is calling.
hooks version
export const LoaderComponent = () => {
const dispatch = useDispatch();
const isLoaded = useSelector(state => state.data === null);
useEffect(() => {
if (!isLoaded) {
dispatch(loadState());
}
}, [dispatch, isLoaded]);
if (isLoaded) {
return <YourCurrentComponent />;
} else {
return <Loading />
}
}
And of course you would remove the fetching actions from the componentDidMount of the current component.

How to save fetched data from server to component state using redux and redux-thunk?

In my react app I have component named profile, and I am fetching data from server and showing it inside that component. I am using redux and redux-thunk along with axios. With help of mapDispatchToProps function, i am calling redux action for fetching that data when component is mounted and saving it to redux state. After that, using mapStateToProps function i am showing that data on the screen via props. That works fine. Now I want to have possibility to edit, for example, first name of that user. To accomplish that i need to save that data to component state when data is fetched from server, and then when text field is changed, component state also needs to be changed. Don't know how to save data to component sate, immediately after it is fetched.
Simplified code:
const mapStateToProps = (state) => {
return {
user: state.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
getUserData: () => dispatch(userActions.getUserData())
}
}
class Profile extends Component {
state:{
user: {}
}
componentDidMount (){
this.props.getUserData()
// when data is saved to redux state i need to save it to component state
}
editTextField = () => {
this.setState({
[e.target.id]: e.target.value
})
};
render(){
const { user } = this.props;
return(
<TextField id="firstName"
value={user.firstName}
onChange={this.editTextField}
/>
)
}
}
You can use componentDidUpdate for that or give a callback function to your action.
I will show both.
First lets see componentDidUpdate,
Here you can compare your previous data and your present data, and if there is some change, you can set your state, for example if you data is an array.
state = {
data: []
}
then inside your componentDidUpdate
componentDidUpdate(prevProps, prevState) {
if(prevProps.data.length !== this.props.data.length) {
// update your state, in your case you just need userData, so you
// can compare something like name or something else, but still
// for better equality check, you can use lodash, it will also check for objects,
this.setState({ data: this.props.data});
}
}
_.isEqual(a, b); // returns false if different
This was one solution, another solution is to pass a call back funtion to your action,
lets say you call this.props.getData()
you can do something like this
this.props.getData((data) => {
this.setState({ data });
})
here you pass your data from redux action to your state.
your redux action would be something like this.
export const getData = (done) => async dispatch => {
const data = await getSomeData(); // or api call
// when you dispatch your action, also call your done
done(data);
}
If you are using React 16.0+, you can use the static method getDerivedStateFromProps. You can read about it react docs.
Using your example:
class Profile extends Component {
// other methods here ...
static getDerivedStateFromProps(props) {
return {
user: props.user
}
}
// other methods here...
}

State not changing after calling this.setState

I am getting the data from my form component and trying to set the state of my app component with this data.
However, the state.data is an empty object and is not updating the data. I console log the model data before setting it to check if it exists. Their is data within the model.
import React, { Component, Fragment } from "react";
import Form from "../components/Form";
import product from "./product.json";
class App extends Component {
constructor() {
super();
this.state = {
data: {}
};
}
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
render() {
const fields = product.fields;
return (
<Fragment>
<div>Header</div>
<Form
model={fields}
onSubmit={(model) => {this.onSubmit(model);}}
/>
<div>Footer</div>
</Fragment>
);
}
}
export default App;
setState() is an async call in React. So you won't likely get the updated state value in the next line. To check the updated value on successful state update, you could check in the callback handler.
Change this
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
to
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
}, () => {
console.log("Form: ", this.state);
});
}
As per the react docs, setState is an asynchronous call. You can ensure your state has updated to perform a particular action in two ways as shown below:
You can pass the setState a function which will have your current state and props and you the value you return will be your next state of the component.
Keep in mind following:
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Following is an example:
this.setState((state, props) => {
//do something
return {counter: state.counter + props.step};
});
You can pass a callback to the setState function as mentioned in Dinesh's
answer. The callback will be executed once the state has been updated successfully hence ensuring you will have the updated state in the call back.
Following is an example:
this.setState({ ...new state }, () => {
// do something
});
Hope it helps.
I just want to add, that if you will do like this its not going to work:
this.setState({things} , console.log(this.state))
You have to pass a refarence to the call back and not the exscutable code itself. If you won't do so, the function will envoke before the state is updated,even you will see the log.

Prop always null in react

I have a component which has to display the object details. This is instantiated from a TableComponent on click of a row.
The object id is passed:
<ObjectDetails objectID = {id}/>
This is my ObjectDetails component:
class ObjectDetails extends Component {
componentWillMount() {
this.props.dispatch(loadObjectDetails(this.props.objectID));
}
render() {
console.log("props------" + JSON.stringify(this.props));
....
}
let select = (state) => ({objectDetails: state.objectDetails});
export default connect(select)(ObjectDetails);
}
loadObjectDetails populates the store with objectDetails. I can see that the store does have the details.
But in render(), props always has objectDetails as null.
Not sure what I'm doing wrong, any help please?
Edit:
Adding few more details
export function loadObjectDetails(objectID) {
return function (dispatch) {
Rest.get('/rest/objects/' + objectID).end(
(err, res) => {
if(err) {
dispatch({ type: 'FETCH_OBJECT_DETAILS_FAILURE', error: res.body})
} else {
dispatch({ type: 'FETCH_OBJECT_DETAILS_SUCCESS', objectDetails: res.body})
}
}
)
}
}
export default function objectDetailsReducer(state={
objectDetails: null,
error: null,
}, action) {
switch (action.type) {
case "FETCH_OBJECT_DETAILS_SUCCESS": {
return {...state, objectDetails: action.objectDetails, error: null}
}
case "FETCH_OBJECT_DETAILS_FAILURE": {
return {...state, error: action.error }
}
}
return state
}
const middleware = applyMiddleware(promise(), thunk, logger())
export default compose(middleware)(createStore)(combineReducers({ reducer1, reducer2, objectDetailsReducer}))
The object inside your mapStateToProps function is what gets passed as props:
{objectDetails: state.objectDetails}
So in your component, you can access: this.props.objectDetails. And whatever properties are on that. If you just wish to pass the objectID, update your mapStateToProps function to change that.
if you want component get new props when store mutates, you have to use mapStateToProps can see many samples in docs: http://redux.js.org/docs/basics/UsageWithReact.html
In your sample code you have extra spaces around = sign:
<ObjectDetails objectID = {id}/>.
componentWillMount function is fired once when component will mount and that's it, so you dispatch this action, it updates the store, but you don't have mapStateToProps so this component has no reaction to store updates.
Component can react to store by mapStateToProps or you can leave this component presentational but then upper component has to have mapStateToProps.
You can read about presentational component on same page: http://redux.js.org/docs/basics/UsageWithReact.html
Component will try to re-render only when props or state is changed. If props/state is not changed - no re-render happens. Your props don't change. You don't use state on this component at all, so nothing changes too.
The render() method was throwing an error on the first render which is why it was not rendering on props change.
It is re-rendering after I fixed that error.
Sorry for wasting your time!

Resources