How to update state value inside Action? - reactjs

I am new in react development,
I have a state variable created inside constructor.
constructor (props) {
super (props);
this.state = {
status: false,
};
}
i am using redux and i have a action method to update redux values.
export const actionMethod = () => {
return dispatch => {
return fetch (
)
.then (response => response.json ())
.then (responseJson => {
// i want to update value of state here.
//i tried this.setState ({status: true}); but its getting TypeError: //_this.setState is not a function
})
.catch (error => {
console.error (error);
});
};
};
Let me how to update status value inside actionMethod.

There are several things wrong here:
A redux action is a function declared somewhere outside of a React component, so it has no access to your Component state or anything else which is outside of its function scope.
You use reducers to update a global redux state, which can trigger automatic rerendering of React components (if used correctly)
I believe you first need a better understanding of react, before trying to integrate redux. You can update a component's state without redux just fine. First learn react and how react components can be updated and rerendered using props and state, then later see if your application could benefit from redux.

Related

Can I use createStore() from redux inside a component?

Question: Is it possible to use createStore() from redux in a component?
If so, how to do it properly?
I'm trying to understand react and redux by tranforming this stopwatch example to a component-based approach.
The original approach is as follows:
let container = Redux.createStore((model = { running: false, time: 0 }, action) => {
const updates = {
'START': (model) => Object.assign(model, {running: true}),
'STOP': (model) => Object.assign(model, {running: false}),
'TICK': (model) => Object.assign(model, {time: model.time + (model.running ? 1 : 0)})
};
return (updates[action.type] || (() => model))(model);
});
And
const render = () => {
ReactDOM.render(view(container.getState()),
document.getElementById('root')
);
};
What I get so far is this, which does not show the counter div.
Note that I'm trying to understand the reasonings behind react and redux, thus avoiding the use of react-redux intentionally.
It's technically possible, but that's not how you should do it.
In React, you want the output of render method to be only dependent on component's props and state. So what you want to do, is to define a component that expects something outside of it to provide the props, and then make Redux be that provider.
You can read more about it here.
There was another issue with your example: Redux wants the reducer (the method you pass to createStore) to not mutate the previous state. Object.assign(state, ...) does mutate object, so it needs to be changed to Object.assign({}, state, ...). More on that here.
I have a working snippet for you, but I strongly recomment following the official guides :)

componentWillReceiveProps not called after redux dispatch

I'm building a react native app and using redux to handle the state. I am running into a situation where one of my containers is not updating immediately when the redux state is changed.
Container:
...
class ContainerClass extends Component<Props, State> {
...
componentWillReceiveProps(nextProps: Object) {
console.log('WILL RECEIVE PROPS:', nextProps);
}
...
render() {
const { data } = this.props;
return <SubComponent data={data} />
}
}
const mapStateToProps = (state) => ({
data: state.data
};
export default connect(mapStateToProps)(ContainerClass);
Reducer:
...
export default function reducer(state = initalState, action) => {
switch(action.type) {
case getType(actions.actionOne):
console.log('SETTING THE STATE');
return { ...state, data: action.payload };
...
...
...
In a different random component, I am dispatching a call with the actionOne action, which I confirm prints out the relevant console.log. However, the console.log in the componentWillReceiveProps in the container is not printed.
The component that dispatches the call is a modal that has appeared over the Container, and closes automatically after the call is dispatched and the state is updated. What is weird is that although the Container isn't updated immediately, if I navigate to a different page and then back to the Container page, the state is in fact updated.
EDIT: Initial state is:
const initialState: Store = {
data: []
}
And the way I dispatch is in a different component which gets called as a new modal (using react-native-navigation) from Container:
fnc() {
...
setData(data.concat(newDatum));
...
}
Where setData and data are the redux dispatch action and the part of the store respectively that is passed in on props from the Container (which has setData and data through mapStateToProps shown above and a mapDispatchToProps which I didn't show).
I solved my problem by updating from react-native v0.56 to v0.57. Apparently there was a problem with react-redux v6 working properly in the react-native v0.56 environment.
Assuming you're using a recent version of React, componentWillReceiveProps is actually deprecated:
Using this lifecycle method often leads to bugs and inconsistencies
You can't really rely on that lifecycle hook in a number of situations. You may want to look at a slightly different approach with componentDidUpdate instead.
I think more important is to get the value after changing in state of redux rather than in which lifecycle you are getting the value . so for getting the value you can use subscribe method of redux in componentDidMount
store.subscribe( ()=> {
var updatedStoreState = store.getState();
})
I believe that getDerivedStateForProps would solve your problem.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.data !== prevState.data) {
//Do something
} else {
//Do something else
}
}
You would check the state from the redux against the state from your component and then act accordingly.
Also, some info from the documentation that you might consider before using this method:
1. getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
2. This method exists for rare use cases where the state depends on changes in props over time.
3. If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
You can read more at: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

How to loop over the redux state data after redux action has completed

I have a basic need about redux actions but I don't know how can i solve it.
This is my scenerio;
I have a redux action like as follows and I want to loop over the data after redux action has completed in my statefull component.
export const fetchServiceTypes = () => {
return dispatch => {
dispatch(fetchStStart());
axios.get(GAMMA_CONSTANTS.LINK_SERVICETYPES)
.then(res => {
dispatch(fetchStSuccess(res.data))
})
})
.catch(err => {
dispatch(fetchStFail(err))
})
}
}
In the component, I call the redux action in the componentWillMount function like as follows but I think, because redux action works asynchronously, i couldn't loop over the redux state as soon as it finished
componentWillMount() {
if (this.props.serviceTypes.length === 0)
this.props.onFetchServiceTypes();
this.props.serviceType.forEach(x => {
// TO DO
})
}
Anybody how to solve that?
Thank you
componentDidMount is only triggered once before the component has rendered, and it doesn't care if your data is there or not. You could listen for a change in the state instead. This article explains how the new lifecycle method works.
Side note: If you are only looking to show the data you can actually loop over it directly in the html and it will be updated whenever the state is updated.

ComponentDidMount not getting called after redux state update?

I think I'm missing a concept here about React and Redux. I'm trying to work with objects stored in redux, and I'm having trouble.
REDUX:
I have an action fetchItems, that gets all items from the database. This action works successfully.
REACT:
I have a container, UserProfile, that calls fetchItems in componentDidMount.
class UserProfile extends Component {
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.props.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.props.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
The problem I'm seeing is that this.props.items is always null (even though fetchItems is successful). The only way I can detect that items were stored in redux store is if I use componentWillRecieveProps(nextProps). Here, I successfully see the items in nextProps. I feel like using componentWillReceiveProps might be too "messy" though. I guess what I'm asking is, what is the standard way of dealing with updates to redux states in react?
Aseel
The cycle will be :
constructor()
componentWillMount() (will be soon deprecated by the way : https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b)
render() => first render (this.props.items, coming from mapStateToProps will be undefined)
componentDidMount() => launching fetchItems() => changing redux state => changing the this.props.items => launching the second render() where this.props.items will be set.
So :
you should have two console.log('[Render]: Items: ', this.props.items);
you should deal with a "loading" state when the this.props.items is null
If the second console.log is still null, Try to add log in your reducer, in the mapStateToProps, ... perhaps it's not state.items.items ...
In react, we have something called state. if the state of a component is changed the component will re-render. Having said that we can use this.setState() inside componentWillRecieveProps to update the state which in turn will rerender the component. So your code will look like this which is the standard way to handle Redux level state changes in react.
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items
}
}
componentWillMount() {
console.log('------------ USER PROFILE -------------------');
}
componentWillRecieveProps({ items }) {
this.setState({ items });
}
componentDidMount() {
console.log('[ComponentDidMount]: Items: ', this.state.items);
this.props.fetchItems();
}
render() {
let profile = null;
console.log('[Render]: Items: ', this.state.items);
return <Auxillary>{profile}</Auxillary>;
}
}
const mapStateToProps = state => {
return {
items: state.items.items
};
};
const mapDispatchToProps = dispatch => {
return {
fetchItems: () => dispatch(actions.fetchItems())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
P.S Just making the API call inside componentWillMount will not help either as API call is async and can take up some time to resolve and till then react will finish rendering the component. so you'll still have to use componentWillRecieveProps
Standard practice is to call this.props.fetchItems() in your constructor or componentWillMount().
componentDidMount is called after render which is why your items do not render - they do not exist until after the initial render.
There are certain ways you can resolve this.
The very first time when render() gets called it was subscribed to the initial props/state that was initialise in redux store through redux connect method. In your case items was null.
Always initialise your redux store with some meaningful data.
In your case if items will be array you can initialise with empty array.
When you dispatch action your store will get updated and the component which was subscribed to items will be re rendered and in this way you donot have to use setState inside componentWillReceiveProps and you can avoid using it.
You need to handle certain cases in render like if array is empty and data is still loading then show some kind of loader and once data is fetched then display it.

Re-render React component when prop changes

I'm trying to separate a presentational component from a container component. I have a SitesTable and a SitesTableContainer. The container is responsible for triggering redux actions to fetch the appropriate sites based on the current user.
The problem is the current user is fetched asynchronously, after the container component gets rendered initially. This means that the container component doesn't know that it needs to re-execute the code in its componentDidMount function which would update the data to send to the SitesTable. I think I need to re-render the container component when one of its props(user) changes. How do I do this correctly?
class SitesTableContainer extends React.Component {
static get propTypes() {
return {
sites: React.PropTypes.object,
user: React.PropTypes.object,
isManager: React.PropTypes.boolean
}
}
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
render() {
return <SitesTable sites={this.props.sites}/>
}
}
function mapStateToProps(state) {
const user = userUtils.getCurrentUser(state)
return {
sites: state.get('sites'),
user,
isManager: userUtils.isManager(user)
}
}
export default connect(mapStateToProps)(SitesTableContainer);
You have to add a condition in your componentDidUpdate method.
The example is using fast-deep-equal to compare the objects.
import equal from 'fast-deep-equal'
...
constructor(){
this.updateUser = this.updateUser.bind(this);
}
componentDidMount() {
this.updateUser();
}
componentDidUpdate(prevProps) {
if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
{
this.updateUser();
}
}
updateUser() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Using Hooks (React 16.8.0+)
import React, { useEffect } from 'react';
const SitesTableContainer = ({
user,
isManager,
dispatch,
sites,
}) => {
useEffect(() => {
if(isManager) {
dispatch(actions.fetchAllSites())
} else {
const currentUserId = user.get('id')
dispatch(actions.fetchUsersSites(currentUserId))
}
}, [user]);
return (
return <SitesTable sites={sites}/>
)
}
If the prop you are comparing is an object or an array, you should use useDeepCompareEffect instead of useEffect.
componentWillReceiveProps() is going to be deprecated in the future due to bugs and inconsistencies. An alternative solution for re-rendering a component on props change is to use componentDidUpdate() and shouldComponentUpdate().
componentDidUpdate() is called whenever the component updates AND if shouldComponentUpdate() returns true (If shouldComponentUpdate() is not defined it returns true by default).
shouldComponentUpdate(nextProps){
return nextProps.changedProp !== this.state.changedProp;
}
componentDidUpdate(props){
// Desired operations: ex setting state
}
This same behavior can be accomplished using only the componentDidUpdate() method by including the conditional statement inside of it.
componentDidUpdate(prevProps){
if(prevProps.changedProp !== this.props.changedProp){
this.setState({
changedProp: this.props.changedProp
});
}
}
If one attempts to set the state without a conditional or without defining shouldComponentUpdate() the component will infinitely re-render
You could use KEY unique key (combination of the data) that changes with props, and that component will be rerendered with updated props.
componentWillReceiveProps(nextProps) { // your code here}
I think that is the event you need. componentWillReceiveProps triggers whenever your component receive something through props. From there you can have your checking then do whatever you want to do.
I would recommend having a look at this answer of mine, and see if it is relevant to what you are doing. If I understand your real problem, it's that your just not using your async action correctly and updating the redux "store", which will automatically update your component with it's new props.
This section of your code:
componentDidMount() {
if (this.props.isManager) {
this.props.dispatch(actions.fetchAllSites())
} else {
const currentUserId = this.props.user.get('id')
this.props.dispatch(actions.fetchUsersSites(currentUserId))
}
}
Should not be triggering in a component, it should be handled after executing your first request.
Have a look at this example from redux-thunk:
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
You don't necessarily have to use redux-thunk, but it will help you reason about scenarios like this and write code to match.
A friendly method to use is the following, once prop updates it will automatically rerender component:
render {
let textWhenComponentUpdate = this.props.text
return (
<View>
<Text>{textWhenComponentUpdate}</Text>
</View>
)
}
You could use the getDerivedStateFromProps() lifecyle method in the component that you want to be re-rendered, to set it's state based on an incoming change to the props passed to the component. Updating the state will cause a re-render. It works like this:
static getDerivedStateFromProps(nextProps, prevState) {
return { myStateProperty: nextProps.myProp};
}
This will set the value for myStateProperty in the component state to the value of myProp, and the component will re-render.
Make sure you understand potential implications of using this approach. In particular, you need to avoid overwriting the state of your component unintentionally because the props were updated in the parent component unexpectedly. You can perform checking logic if required by comparing the existing state (represented by prevState), to any incoming props value(s).
Only use an updated prop to update the state in cases where the value from props is the source of truth for the state value. If that's the case, there may also be a simpler way to achieve what you need. See - You Probably Don't Need Derived State – React Blog.

Resources