简体   繁体   中英

Cant update state of parent from deeply nested children in React js

I'm trying to construct some SVG of a tree (a json-object) recursively using react components. Every node (a tree's node) component returns other nodes (and edges) until there are no more child-nodes. The tree renders perfectly on my screen.

Now I want to count all the leafe-nodes using a function wich I pass down via props to all the nodes. When a node got no child-nodes, it calls the function in its constructor. With this function I want to increase this.state.countLeafs of my main component using this.setState. The function got called as often as there are leafe nodes in my tree (I'm checking it with console.log). For every leaf node the function got called, but my state does not update.

my main component:

import React, { Component } from 'react';
import SpecNode from './graphicTreeComponents/SpecNode.js';

class GraphicController extends Component {
    constructor(props){
        super(props);
        this.incLeafs = this.incLeafs.bind(this);

        this.state = ({
            u: {key: "0", x: 100, y: 0, k: 
                    [
                    {key: "1", x: 40, y: 100, k: null},
                    {key: "2", x: 160, y: 100, k: null}
                    ]
                },
            countLeafs: 0
        })
    }
    incLeafs(){
        console.log("leafs++");
        this.setState({countLeafs: this.state.countLeafs + 1});
        console.log(this.state.countLeafs);
    }
    render(props){
        return (
            <div className="GraphicController">
                <SpecNode {...this.state.u} incLeafs={this.incLeafs}/>
            </div>
        );
    }
}
export default GraphicController;

and my node component:

import React, { Component } from 'react';
import Edge from './Edge.js';
import SpecNode2 from './SpecNode.js';

class SpecNode extends Component{

    constructor(props){
        super(props);

        if(!this.props.k){
            this.props.incLeafs();
        }
    }

    render(props){
        return(
            <svg>
                {this.props.k ? 
                    this.props.k.map(e =>
                        <svg>
                            <Edge x1={this.props.x} y1={this.props.y} x2={e.x} y2={e.y} />
                            <SpecNode2 {...e} incLeafs={this.props.incLeafs}/>
                            <circle cx={e.x} cy={e.y} r={5} fill={"lightgrey"} />
                            <text fontSize={7} x={e.x-2} y={e.y+3} fill="black">{e.key}</text>
                        </svg>
                    ) 
                    :
                    null
                }
            </svg>
        );
    }
}
export default SpecNode;

Is there anything fundamental I miss?

Thank you

Are you sure the leafCount is not updating? setState is asynchronous, but it takes a callback. Your console.log might be happening before your state is updated. You should try something like...

incLeafs(){
    console.log("leafs++");
    this.setState({countLeafs: this.state.countLeafs + 1}, () => {
        console.log(this.state.countLeafs);
    });   
}

In your main component, you don't need the parentheses when initializing state.

this.state = {
        u: {key: "0", x: 100, y: 0, k: 
                [
                {key: "1", x: 40, y: 100, k: null},
                {key: "2", x: 160, y: 100, k: null}
                ]
            },
        countLeafs: 0
    }

You could also try moving your function call to componentDidMount within the SpecNode component

constructor(props){
    super(props);
}
componentDidMount() {
    if(!this.props.k){
        this.props.incLeafs();
    }
}

Your method this.incLeafs is not bound to the parent component. Because of that, when it runs, this doesn't mean the GraphicController when the child node calls the method.

Either

A) change the way you've written the method on the parent class into an arrow function, like this: incLeafs = () => {...};

B) manually bind the method method at some point with something like this.incLeafsHandler = this.incLeafs.bind(this) and then pass this.incLeafsHandler as the incLeaf callback to SpecNode. ((I strongly don't like this solution, but that's just my preference.))

C), pass it down as an arrow function in the props (as <SpecNode ... incLeafs={() => this.incLeafs()} /> )

Arrow functions always retain this as whatever it was when the arrow functions were declared. ( Options A and C )

Normal functions will change this based on where they're getting called, unless you use a bound copy of the original method. ( Option B )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM