Inconsistent rendering of nested react-redux-connected components - reactjs

I think we've been bitten by this issue (Strategy for avoiding cascading renders), but I don't know quite what to do about it.
In short, a child component is receiving property updates and executing a parent's bound render callback before the parent has received its prop updates. The result is that the callback uses stale properties and we get inconsistent rendering.
Here's a rough representation:
class Parent extends React.Component {
renderHeader = () => {
return <<uses this.props to render a header>>
}
render () {
return ( <Child renderHeader={this.renderHeader}/> )
}
}
function mapStateToProps(state) {
return Object.assign({}, state.tree.branch)
}
export default connect(mapStateToProps)(Parent);
...
class Child extends React.Component {
render () {
return (
...
this.props.renderHeader()
...
)
}
}
function mapStateToProps(state) {
return Object.assign({}, state.tree.branch)
}
export default connect(mapStateToProps)(Child);
I can make this work by passing props to the callback explicitly or by just avoid this pattern, but I feel like there's a larger concept/pattern/anti-pattern going on here that I'm failing to understand. Questions:
Is it actually invalid to connect both components to the state tree?
Is there clear logic for whether parents or children receive props first?

Related

how to update both parent and child state before render without extra renders in React 15

If I use setState in the child and place a callback in the parent to update the parent state that propagates to child props, then I end up with two render calls.
One for the update to the child state, and one for the prop changing. I can manually use shouldComponentUpdate to ignore the prop change if I want, but the render won't be ready until the state updates.
I know all this can be done easily in react 16-18, but migrating is not simple at the moment.
I am wondering if the solution is to make the child state the source of truth. I can solve all my problems this way, but I thought in react you typically made the parent the source of truth.
Parent Component
Child Component
ChildComponent
function = () => {
this.setState ( {updatedStateProperty}, callback())
}
ParentComponent
callback = () => {
this.setState ( {propSentToChild})
}
What happens is the child component changes state, then render occurs, then the callback occurs, prompting another render.
I want to either
A. change child state, then have the callback called before render
or
B. update child state, then ignore the parents passed props
I can do B, but I'm unsure whether it is proper form to basically make the child's version of the shared state the source of truth
I think you're kind of close. What you really want to do is pass the state to the parent, handle setting the state there, and let the new state trickle down to your child component via props. This is a fairly common pattern for react.
class Parent extends React.Component {
constructor() {
this.state = { foo: "bar", bing: "baz" }
}
stateUpdater(newState) {
this.setState({ ...this.state, ...newState });
}
render() {
return <Child
prop1={this.state.foo}
prop2={this.state.baz}
stateUpdater={this.stateUpdater}
/>
}
}
class Child extends React.Component {
handleClick = () => {
this.props.stateUpdater({ foo: 'bazaar' });
}
render() {
return <div>
The foo is {this.props.foo} and the baz is {this.props.baz}.
<button onClick={this.handleClick}>Click Me!</button>
</div>
}
}

React Higher Order Component With Redux causes infinite loop

I'm trying to show loading while data is fetching from API and I want a higher-order component to achieve this. But my code causes an infinite loop
//home.tsx file
export class Home extends Component<Props, any> {
componentDidMount(): void {
this.props.getCharacters();
}
render() {
return (
<div></div>
)
}
}
const mapStateToProps = (state: AppState): StateProps => {
return {
loading:state.marvel.loading,
data: getResultsSelector(state),
};
};
const mapDispatchToProps: DispatchProps = {
getCharacters,
};
export default connect(mapStateToProps, mapDispatchToProps)(WithLoading(Home));
//hoc.tsx file
function WithLoading(Component:any) {
return function WihLoadingComponent({ loading, ...props }:any) {
console.log(loading)
if (!loading) return <Component {...props} />;
return <p>Hold on, fetching data might take some time.</p>;
};
}
export default WithLoading;
How can I fix this issue ?
I assume that getCharacters() is the function that fetches data from the API.
The Component is mounted depending on loading, but loading is set when the Component is mounted.
if (!loading) return <Component />; mounts the component.
componentDidMount invokes getCharacters()
getCharacters() sets loading = true
if (!loading) return <Component />; does not return the Component anymore.
when loading is done: loading = false
if (!loading) return <Component />; mounts the component again.
continue at 2.
The loading and mounting must be made independent of each other.
A. Either you should not invoke the loading inside the Component,
B. or you should not mount the Component conditionally.
A: separate the loading from the Component
As you probably want to create a generic HOC that shows some arbitrary Component only when loaded === true, you might prefer A., which means you have to design the Components so that they do not change loading themselves.
There might be ways to do that, e.g.:
componentDidMount(): void {
if( !this.props.dataHasAlreadyBeFetched ){
this.props.getCharacters();
}
}
but I think that would be bad style, and it seems to me that in your case it makes sense to separate it, because apparently the only reason your Component is mounted the first time is to invoke getCharacters, which unmounts the Component.
B: not un-mount the Component
The Component is always mounted (without if( !loading)), and itself has to be designed to render any content only if loading === true (and otherwise null). Of course, loading has to be passed to the Component as well.

Why aren't parent Props Equal to Child state when Child state actually reference props from parent

I am passing props from Parent component into Child's state but They are out of sync.
What I tried:
State Updates May Be Asynchronous, I have taken care of that using a call back instead of returning an object.
Objects are passed by reference, but the prop i used is a string.
I am using React 16 and es6 syntax
class Parent extends React.Component {
state = {
isHidden: false
}
render() {
console.log('PROPS from PARENT', this.state.isHidden)
return <div>
<Child isOpen={this.state.isHidden} />
<button onClick={this.toggleModal}>Toggle Modal</button>
</div>
}
toggleModal = () => this.setState(state => ({isHidden: !state.isHidden}))
}
class Child extends React.Component {
state = {
isHidden: this.props.isOpen
}
render() {
console.log('STATE of CHILD:',this.state.isHidden)
return <p hidden={this.state.isHidden}>Hidden:{this.state.isHidden}</p>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
Here a codepen PEN - notice the redered element is supposed to be hidden based on the state(state depends on props from parent)
Use componentWillReceiveProps which call when changes in props occur.
class Child extends React.Component {
state = {
isHidden: this.props.isOpen
}
componentWillReceiveProps(props) {
if (props.isOpen != this.state.isHidden)
this.setState({
isHidden: props.isOpen
})
}
render() {
console.log('STATE of CHILD:', this.state.isHidden)
return <p hidden = {
this.state.isHidden
} > Hidden: {
this.state.isHidden
} < /p>
}
}
If you remove the state definition from the child, which is not needed, and use only the props that is passed from the parent, I believe that the behaviour of the child will make sense.
If you want to use state in the child, the constructor setting is not enough, you need the set the child state when props changes.
Console.log is asynchronous, so you cannot rely on it here.
Your Child's state does not change with prop change since component does not know anything about state change in your constructor. This is a common pitfall when you depend on your props to construct your local state. You can use componentWillReceiveProps as shown in #Nishant Dixit's answer. But, starting with React 16.3 we have getDerivedStateFromProps function (lifecylce method) for this.
static getDerivedStateFromProps( props, state) {
if( props.isOpen === state.isHidden) {
return null;
}
return {
isHidden: props.isOpen,
}
}
Here, we are comparing our prop and state, if there is a change we are returning desired state. No need to use this.setState.
Related API change blog post including async rendering:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
Although the other answers will make the code work, there is actually a more elegant solution :)
Your child component does not need any state as the state is managed by the Parent (which manages the isHidden property and passes it to the child). So the child component should only care about props.
Try writing the component like this and I believe it should work:
class Child extends React.Component {
render() {
return <p hidden={this.props.isHidden}>Hidden:{this.props.isHidden}</p>
}
}
Dan Abramov who works on the React team tweeted about this problem - essentially saying that you should think hard about whether you can just use props before using state in a component
https://twitter.com/dan_abramov/status/979520339968516097?lang=en

React / Redux Components not re-rendering on state change

I think this question has been answer several time but I can't find my specific case.
https://codesandbox.io/s/jjy9l3003
So basically I have an App component that trigger an action that change a state call "isSmall" to true if the screen is resized and less than 500px (and false if it is higher)
class App extends React.Component {
...
resizeHandeler(e) {
const { window, dispatch } = this.props;
if (window.innerWidth < 500 && !this.state.isSmall) {
dispatch(isSmallAction(true));
this.setState({ isSmall: true });
} else if (window.innerWidth >= 500 && this.state.isSmall) {
dispatch(isSmallAction(false));
console.log(isSmallAction(false));
this.setState({ isSmall: false })
}
};
componentDidMount() {
const { window } = this.props;
window.addEventListener('resize', this.resizeHandeler.bind(this));
}
...
I have an other component called HeaderContainer who is a child of App and connected to the Store and the state "isSmall", I want this component to rerender when the "isSmall" change state... but it is not
class Header extends React.Component {
constructor(props) {
super(props);
this.isSmall = props.isSmall;
this.isHome = props.isHome;
}
...
render() {
return (
<div>
{
this.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
...
even if I can see through the console that redux is actually updating the store the Header component is not re-rendering.
Can someone point out what I am missing ?
Am I misunderstanding the "connect()" redux-react function ?
Looking at your code on the link you posted your component is connected to the redux store via connect
const mapStateToProps = (state, ownProps) => {
return {
isHome: ownProps.isHome,
isSmall: state.get('isSmall')
}
}
export const HeaderContainer = connect(mapStateToProps)(Header);
That means that the props you are accessing in your mapStateToProps function (isHome and isSmall) are taken from the redux store and passed as props into your components.
To have React re-render your component you have to use 'this.props' inside the render function (as render is called every time a prop change):
render() {
return (
<div>
{
this.props.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
You are doing it well in the constructor but the constructor is only called once before the component is mounted. You should have a look at react lifecycle methods: https://reactjs.org/docs/react-component.html#constructor
You could remove entirely the constructor in your Header.js file.
You should also avoid using public class properties (e.g. this.isSmall = props.isSmall; ) in react when possible and make use of the React local state when your component needs it: https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
A component is only mounted once and then only being updated by getting passed new props. You constructor is therefore only being called once before mount. That means that the instance properties you set there will never change during the lifetime of your mounted component. You have to directly Access this.props in your render() function to make updating work. You can remove the constructor as he doesn't do anything useful in this case.

Redux: Update parent component data after child operations

I have some data loaded in the store after initial Axios call.
Then I render two components match (parent component) and player (child component).
This is the way to show the two components in a related way (this is a simplified example from my original code, in this example I could solve my problem in another way, but in my complex real code it is essential to do an operations in children component first):
match.js
class Match extends Component {
constructor(props) {
super(props);
}
render() {
return (
Object.values(this.props.matchs).map(( match_id ) => {
let match = this.props.matchs[match_id];
return (
<div key={match_id}>
<p>{match.tournament}</p>
<p>{match.color}</p> {/* this color depends of children condition*/ }
<div className="players">
{match.array_players.map ( ( player_id ) => {
let player = this.props.players[player_id];
return (
<Player key={odd_id} ownPlayer={player} />
)
})
</div>
</div>
)
});
)
}
}
const mapStateToProps = state => {
return {
matchs: state.matchs.matchs,
players: state.players.players
};
}
const mapDispatchToProps = dispatch => {
return {
// actions
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Matchs);
player.js
class Player extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>{this.props.ownPlayer.name}</p>
<p>{this.props.player_color}</p>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
// I need to make some previous operations before render
let player_color;
if (ownProps.ownPlayer.name == "paul")
player_color = 'yellow';
else
player_color = 'blue';
// Then I Need to update parent component with children color condition
// if (player_color == 'yellow')
// match_color = 'yellow'
//
// Call some action here to update parent component???
// things like these do not work:
// let id_p = ownProps.player.id_player;
// state.players.players[id_p].color = 'blue'; This does not work
return {
player_color
};
};
const mapDispatchToProps = dispatch => {
return {
//
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Player);
Then I need to update a prop in a parent component after some conditions in children component.
I've read this article:
https://redux.js.org/docs/recipes/ComputingDerivedData.html
But I don't know how to send data to store and refresh parent component before render.
I thought about calling like an action in componentWillMount or componentWillUpdate to send data to store, but I don't know if it's correct way.
There is nothing wrong with calling an action inside the lifecycle, it is not recommended to do it inside the render method because it my trigger infinite actions, but in your situation if you indeed have to do this calculation inside the child component I believe you should dispatch this action inside componentWillReceiveProps or ComponentDidMount, in some situations you actually have to do it in both places.
go for it!
The docs are pretty clear:
You can either do one-time ops in constructor / ComponentWillMount / ComponentDidMount or repetitive ops in recurring life-cycle methods like ComponentWillReceiveProps.
If you need a way for the child component to update the store, than you have to dispatch an action that will go and do so, and put it in ComponentWillMount or ComponentWillReceiveProps depending on the need, sometimes you need to put it in both.
But, on a side note, like Bruno Braga said, it does seem like the wrong place to put logic in.
I would suggest to put this logic in the reducer, as Component really shouldn't handle store logic, just notify (dispatch) state changes.
Also, I don't think that you need to connect the Player component to the redux store, since it seems like each player has it's own independent instance.
What I would suggest is passing the Player Component a function from the Match Component, something like
<Player ... onGoalScored={()=> this.handleGoalScored()} />
and on the Match component do:
handleGoalScore() {
this.props.dispatch(updateGoalsAction())
}
and have the logic in the reducer, the let's say will figure out what the color of Match should be, and, on the next state update to Match, because of the binding to store.matchs.color will be rendered as Red

Resources