I want my component know if some library is already loaded. To know that from any context i connect it to the "library" reducer of my store to my component.
I also pass it a configuration object this.props.dataObject
from the parent where the component has been called. Like this:
class GoogleButton extends Component {
render() {
if (this.props.libraries.google) {
return <a id='sharePost' className='google_icon'></a>
} else {
return null
}
}
componentDidUpdate() {
gapi.interactivepost.render('sharePost', this.props.dataObject)
}
}
function mapStateToProps(state) {
return { libraries: state.libraries }
}
export default connect(mapStateToProps)(GoogleButton)
The reducer that handles the libraries state is like this:
let newState = {...state}
newState[action.libraryName] = action.state
return newState
When I change the library state componentDidUpdate
works. The problem is when i change the prop inherited by the parent this.props.dataObject
. In that case is where componentDidUpdate wont fire. If i remove the connect
from the component it works as espected. I'm missing something here?
I want my component know if some library is already loaded. To know that from any context i connect it to the "library" reducer of my store to my component.
I also pass it a configuration object this.props.dataObject
from the parent where the component has been called. Like this:
class GoogleButton extends Component {
render() {
if (this.props.libraries.google) {
return <a id='sharePost' className='google_icon'></a>
} else {
return null
}
}
componentDidUpdate() {
gapi.interactivepost.render('sharePost', this.props.dataObject)
}
}
function mapStateToProps(state) {
return { libraries: state.libraries }
}
export default connect(mapStateToProps)(GoogleButton)
The reducer that handles the libraries state is like this:
let newState = {...state}
newState[action.libraryName] = action.state
return newState
When I change the library state componentDidUpdate
works. The problem is when i change the prop inherited by the parent this.props.dataObject
. In that case is where componentDidUpdate wont fire. If i remove the connect
from the component it works as espected. I'm missing something here?
5 Answers
Reset to default 8Most likely some of your props are mutated outside the component.
For example, you might be rendering your component like this:
class Parent extends Component {
constructor() {
super()
this.state = { libraries: {} }
}
handleClick() {
// MUTATION!
this.state.libraries.google = true
// Normally this forces to update component anyway,
// but React Redux will assume you never mutate
// for performance reasons.
this.setState({ libraries: this.state.libraries })
}
render() {
return (
<div onClick={() => this.handleClick()}>
<GoogleButton libraries={this.state.libraries} />
</div>
)
}
}
Because Redux apps deal with immutable data, connect()
uses shallow equality check for its props to avoid unnecessary re-renders. However, this won’t work if you use mutation in your app.
You have two options:
Don’t Mutate Anything
This is the best option. For example, instead of something like
handleClick() {
this.state.libraries.google = true
this.setState({ libraries: this.state.libraries })
}
you can write
handleClick() {
this.setState({
libraries: {
...this.state.libraries,
google: true
}
})
}
This way we are creating a new object so connect()
wouldn’t ignore the changed reference. (I’m using the object spread syntax in this snippet.)
Disable Performance Optimizations
A worse alternative is to completely disable performance optimizations made by connect()
. Then your props would update even if you mutate them in the parent, but your app will be slower. To do this, replace
export default connect(mapStateToProps)(GoogleButton)
with
export default connect(mapStateToProps, null, null, { pure: false })(GoogleButton)
Don’t do this unless absolutely necessary.
I solved it. I'm not 100% sure that this is accurate, but I will explain. If im wrong with something, please correct me.
I keep thinking about the shallow equality check that Dan said in his answer. The problem was there. I was passing down an object from the parent and the nested elements of that object were the ones that changed. The object remain the same. So with the shallow equality check that connect brings the component will never update.
My solution was in the parent use Object.assign({}, dataObject)
when I pass down the prop so I make another different object. Now shallow equality check could compare it and determinate that the props have changed and there before update the component.
i had same problem and i used object.assign for create new state but i use combineReducer and it cause multi level state ,in my case i pass whole state as props to component so shallow equality check
can not detect my state change so componentDidUpdate didnot call,it is important to pass state in level it change when using combine reducer
in my case i pass it like this
const MapStateToProps=(state)=>{
return {
reportConfig:state.ReportReducer
}
};
and my state tree is like this
{
ReportReducer: {
reportConfig: {
reportDateFilter: 'this week',
reportType: null,
reportShopId: null,
updateShop: true
}
}
}
and in my reducer and return it like this as ReportReducer
export default combineReducers({reportConfig});
and my root reducer is like this
const rootReducer =combineReducers({ReportReducer});
const store = createStore(rootReducer ,{},enhancer);
Another option that you can use is to make a deep copy of the inherit prop this.props.dataObject
on the child component, this in order for the componentDidUpdate
to 'catch' the updated prop, you could use:
dataObject={JSON.parse(JSON.stringify(valueToPass))}
Use this where you are passing the prop from the parent component, this works for me in a similar problem (This applies when you don't have any function inside the prop).
I had this exact same problem with Components I used from an external library. So I didn't had the option to modify the inherited property.
I only needed a part of the inherited property object (will use dataObject
for simplicity). Solved it by adding it to the mapStateToProps
function:
function mapStateToProps(state, ownProps) {
return { libraries: state.libraries, neededValue: ownProps.dataObject.value }
}
By which a shallow compare is enough to notice a value change. So use this.props.neededValue
iso this.props.dataObject.value
in the render()
function.
GoogleButton
? Also you might want to check the other lifecycle methods:componentDidMount
,componentWillReceiveProps
– Scarysize Commented Jun 4, 2016 at 7:38