in my react component, I have two attributes in the state, one in local react state and the other in Redux store.
componentWillMount() {
this.props.fetchExercise(this.props.params.id);
}
constructor(props) {
super(props);
this.state = {editeMode: false}
}
function mapStateToProps(state) {
return {currentExercise: state.currentExercise}
}
export default connect(mapStateToProps, {fetchExercise})(createNewExercisePage);
so according to the path; /new-exe/:id currentExercise in Redux is either empty or something is fetched. editeMode is in React. now I want to check if I have something in currentExercise editemode:true else it should be false (according to false and true I am showing different buttons).
I tried it (with lodash) in componentWillMount(){... this.setState({editeMode:_.isNull(this.props.currentExercise)})}
but it does not work, it reamins false.
generaly in these cases that first should fetch something then check it, what should be the approach.
You should avoid introducing any side-effects or subscriptions in componentWillMount (docs). The documentation also says that "setting state in this method will not trigger a re-rendering", so I guess that means that the setted value will be ignored.
You are not going to change the value of the editeMode entry in the store unless the value of this.props.currentExercise changes, and so it does not serve much purpose to keep track of the changes in order to update the store. Just use the value directly. In your particular case, I would do the following:
componentWillMount() {
this.props.fetchExercise(this.props.params.id);
}
constructor(props) {
super(props);
this.state = {}
}
render(){
const editeMode = _.isNull(this.props.currentExercise);
// The rest of your render logic, using editeMode instead of this.state.editeMode
}
function mapStateToProps(state) {
return {currentExercise: state.currentExercise}
}
export default connect(mapStateToProps, {fetchExercise})(createNewExercisePage);
Put the code in
componentWillReceiveProps.
componentWillReceiveProps(nextProps) {
this.setState({ editeMode: !nextProps.currentExercise) });
}
Redux will make sure the props get updated.
You should also consider putting the editMode state in Redux instead.
Related
Overview
We have a page with a header (Blue color) and content section (Green color) that can be seen in image below. The requirement is when a user selects a year in header, then the content page will show data as per the selected year.
What is happening right now Technically
When user selects a year in header, we dispatch the selected value and the active container's mapStateToProps function is triggered and the selected year is passed to the component.
class Page1Content extends Component {
}
function mapStateToProps(state) {
return { selectedYear : state.userSelectedValue };
}
export default connect(mapStateToProps, null)(Page1Content);
Question 1
How will data on Page1Content will refresh? Few approaches:
In ComponentDidUpdate react life cycle method of the Page1Content API to fetch data can be called. However have seen opinions where we should avoid React hooks and life cycle methods with Redux.
In mapStateToProps function API can be called.
Can any one suggest what is the better place to call the API?
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
In case we decide to use ComponentDidUpdate should we again dispatch the API call using Thunk or any other library and then catch the response in mapStatesToProps again?
Or we should make the API call and resolve it in the component itself as a promise. Then the response will be set in State and respective Template will be refreshed.
ComponentDidUpdate is a lifecycle method not a hook. Hooks is functionality that allows functional components to have class based functionality such as state.
You are using a class based component in your example so you are not using hooks.
https://reactjs.org/docs/hooks-intro.html
Yes Redux shouldnt be used with hooks since context is a better option.
You can lift state up so to speak and update the local state in the parent component getting rid of redux completely.
Just pass down the setState function and the state itself to the appropriate children.
class App extends Component {
constructor(props) {
super(props);
this.state = {
some_prop: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({some_prop: true})
console.log('Click happened');
}
render() {
return (
<Header onClick={this.handleClick } />
<Page1Component props={this.state.some_prop} />
}
}
Edit:
Question 1
How will data on Page1Content will refresh?
best option is with a ternary expression in your render method, there is no need to check if the state updated. In react if the state is changed your component will automatically re render.
render() {
return (
<div>
{this.props.selectedYear
? <p> {this.props.selectedYear}</p>
: null
}
</div>
}
}
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
If I understand this correctly you will need to use an action creators, redux thunk is overkill here.
class Header extends Component {
constructor(props) {
super(props);
}
handleClick() {
this.props.dispatchActionCreator({some_value})
console.log('Click happened');
}
render() {
return (
<button onClick={(some_value) => this.handleClick(some_value)}>Click </button>
}
}
function mapDispatchToProps(state) {
return {
dispatchActioNCreator: (some_value) => dispatch(ACTIONS.action_creator(some_value) };
}
This will save your value from your header to the global redux state and then you can just access with mapStateToProps in your Page1Component.
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.
Complete Reactjs newbie here. I know setState() is asynchronous. I know if I do setState() then the states are queued and batched, so I will not see the state changed immediately. Fair enough.
I have also read the following link and other questions in SO:
https://reactjs.org/docs/react-component.html#setstate
I can use: callback methods in setState, componentDidUpdate() lifecycle method, concat if the state is an array etc. I get all these ways. My problem is a simple one, also I am banging my head on this issue for past 2 days so I am literally at my wit's end now.
I have this logic:
class ItemList extends React.Component {
constructor(props) {
super(props);
this.state = {showCompleted: false};
this.shouldShowFullList = false;
this.onShowCompleted = this.onShowCompleted.bind(this);
}
onShowCompleted(event) {
this.setState({showCompleted: event}, () => {this.shouldShowFullList = this.state.showCompleted;});
}
render() {
if (this.shouldShowFullList) {
return this.renderAllItemsIncludingCompleted();
} else {
return this.renderOnlyNotCompletedItems();
}
}
...
The logic is self-explanatory. My problem is even when I call this.shouldShowFullList in callback method of setState(), it still does not show an updated value. Value of this.shouldShowFullList is false when it should be true and vice versa. What is the best way to have the value of this.shouldShowFullList in lockstep with the this.state.showCompleted?
NOTE: onShowCompleted() is a callback method triggered from a child component. When a checkbox called "Show Completed" is checked, I should show a complete list of items, or else just the items which are not completed - something like ToDo list.
At onShowCompleted do
this.setState({ showCompleted: true })
or if you want toggle the value
this.setState({ showCompleted: !this.state.showCompleted })
Then in the render method you can do
if (this.state.showCompleted) {
return this.renderAllItemsIncludingCompleted();
} else {
return this.renderOnlyNotCompletedItems();
}
When you set a state using setState the method render is called with this.state updated. I think the callback (second argument) of this.setState is called after the render.
Because updating this.state make a new render it seems that react is pushing you to use this.state in the render method. In fact it's made for this usage. If you want to make a variable that have no purpose in the render you can use this.myVariable. Best pratice is to use only this.stateand this.props in the render (or function that depend of it).
I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/
class ApiWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
response: {
"title": 'nothing fetched yet'
}
};
}
componentDidMount() {
this._makeApiCall(this.props.endpoint);
}
_makeApiCall(endpoint) {
fetch(endpoint).then(function(response) {
this.setState({
response: response
});
}.bind(this))
}
render() {
return <Child data = {
this.state.response
}
/>;
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
console.log(this.state.data, 'new data');
return ( < span > {
this.state.data.title
} < /span>);
};
}
var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;
ReactDOM.render(
element,
document.getElementById('container')
);
But for some reason, it seems the child component is not updating when the parent state changes.
Am I missing something here?
There are two issues with your code.
Your child component's initial state is set from props.
this.state = {
data: props.data
};
Quoting from this SO Answer:
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is only called the first time the
component renders. Never more. Meaning that, if you re-render that
component passing a different value as a prop, the component
will not react accordingly, because the component will keep the state
from the first time it was rendered. It's very error prone.
So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.
Adding the below code to your child component will solve your problem with Child component re-rendering.
componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}
The second issue is with the fetch.
_makeApiCall(endpoint) {
fetch(endpoint)
.then((response) => response.json()) // ----> you missed this part
.then((response) => this.setState({ response }));
}
And here is a working fiddle: https://jsfiddle.net/o8b04mLy/
If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.
For example: -
Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
return cartItems.map(item => {
if(item.id===existingItem.id){
++item.quantity;
}
// I can simply return item but instead I spread the item and return a new object
return {...item}
})
}
Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places.
Hope this helped. Let me know if I'm wrong somewhere :)
There are some things you need to change.
When fetch get the response, it is not a json.
I was looking for how can I get this json and I discovered this link.
By the other side, you need to think that constructor function is called only once.
So, you need to change the way that you retrieve the data in <Child> component.
Here, I left an example code: https://jsfiddle.net/emq1ztqj/
I hope that helps.
Accepted answer and componentWillReceiveProps
The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()
Using derived state logic in React
As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State
Current solution: getDerivedStateFromProps
If you choose to use derived state, current solution is using getDerivedStateFromProps call as #DiogoSanto said.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()
How to use componentWillReceiveProps
This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.
class Child extends React.Component {
constructor() {
super(props);
// nothing changed, assign the state for the
// first time to teach its initial shape.
// (it will work without this, but will throw
// a warning)
this.state = {
data: props.data
};
}
componentWillReceiveProps(props) {
// return the new state as object, do not call .setState()
return {
data: props.data
};
}
render() {
// nothing changed, will be called after
// componentWillReceiveProps returned the new state,
// each time props are updated.
return (
<span>{this.state.data.title}</span>
);
}
}
Caution
Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
Derived state logic can make components harder to understand, think on. Use wisely.
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.