Mushood Hanif
Mushood Hanif

Reputation: 711

Cannot change state values using setState()

I'm trying to get values from a form component using props into the main App component where my state is. Using the values i get from the props, i want to update the main state. This is my code:

class App extends Component { state = { dataset: "", cc: "", cw: 0, ch: 0, bw: 0, bh: 0, xspacing: 0, yspacing: 0, barcolor: "", };

  // Function to use the data got from form component and setState
  dataForm = (
    dataset,
    canvascolor,
    canvaswidth,
    canvasheight,
    barwidth,
    barheight,
    xspacing,
    yspacing,
    barcolor
  ) => {
    this.setState({
      dataset,
      canvascolor,
      canvaswidth,
      canvasheight,
      barwidth,
      barheight,
      xspacing,
      yspacing,
      barcolor,
    });
    console.log(this.state);
  };

  render() {
    return (
      <div className="App">
        {/* This is the Form Component from where i'm getting my Data */}
        <DataForm dataForm={this.dataForm} />

        <DataGraph graph={this.state} />
      </div>
    );
  }
}

I am trying to send my updated state to the DataGraph component using props but it doesn't work, This is my DataGraph component code below:

class DataGraph extends Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  drawChart() {
    const d = this.props.graph.dataset.split`,`.map((x) => +x);
    const accessToRef = d3
      .select(this.myRef.current)
      .append("svg")
      .attr("width", this.props.graph.cw)
      .attr("height", this.props.graph.ch)
      .style("background-color", this.props.graph.cc);

    accessToRef
      .selectAll("rect")
      .data(d)
      .enter()
      .append("rect")
      .attr("x", (d, i) => i * this.props.graph.xspacing)
      .attr("y", (d, i) => this.props.graph.ch - this.props.graph.yspacing * d)
      .attr("width", this.props.graph.bw)
      .attr("height", (d, i) => d * this.props.graph.bh)
      .attr("fill", this.props.graph.barcolor);
  }

  componentDidMount() {
    this.drawChart();
  }

  render() {
    return (
      <>
        <div
          style={{
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
          ref={this.myRef}
          className="pb-5"
        ></div>
      </>
    );
  }
}

DataGraph.propTypes = {
  graph: PropTypes.object.isRequired,
};

The setState() does not set the state values. Please help.

Upvotes: 0

Views: 175

Answers (3)

Mushood Hanif
Mushood Hanif

Reputation: 711

The Solution is to use the getDerivedStateFromProps() Static Method to get the State from the Parent component as props and use those props to initialize the child component's state. Here is the Code:

Passing Parent Component State as Props to Child Component:

<DataGraph graph={this.state} />

Using the Above Mentioned Method to Update the Child Component State:

class DataGraph extends Component {
  constructor() {
    super();
    this.state = {
      dataset: "",
      cc: "",
      cw: 0,
      ch: 0,
      bw: 0,
      bh: 0,
      xspacing: 0,
      yspacing: 0,
      barcolor: "",
    };
    this.myRef = React.createRef();
  }

  static getDerivedStateFromProps(props, state) {
    return {
      dataset: props.graph.dataset,
      cc: props.graph.cc,
      cw: props.graph.cw,
      ch: props.graph.ch,
      bw: props.graph.bw,
      bh: props.graph.bh,
      xspacing: props.graph.xspacing,
      yspacing: props.graph.yspacing,
      barcolor: props.graph.barcolor,
    };
  }

  drawChart() {
    const d = this.state.dataset.split`,`.map((x) => +x);
    const accessToRef = d3
      .select(this.myRef.current)
      .append("svg")
      .attr("width", this.state.cw)
      .attr("height", this.state.ch)
      .style("background-color", this.state.cc);

    accessToRef
      .selectAll("rect")
      .data(d)
      .enter()
      .append("rect")
      .attr("x", (d, i) => i * this.state.xspacing)
      .attr("y", (d, i) => this.state.ch - this.state.yspacing * d)
      .attr("width", this.state.bw)
      .attr("height", (d, i) => d * this.state.bh)
      .attr("fill", this.state.barcolor);
  }

  render() {
    this.drawChart();
    return (
      <>
        <div
          style={{
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
          ref={this.myRef}
          className="pb-5"
        ></div>
      </>
    );
  }
}
The getDerivedStateFromProps() is a Static Method as Mentioned Above, so Keep that in Mind While using it. Also, do not initialize "props" in the constructor() or the super() otherwise it won't work. Thankgod it worked, I hate Redux Boilerplate 😅.

Upvotes: 0

Ali Asgher Badshah
Ali Asgher Badshah

Reputation: 851

the main problem due to which your component is not reloading is due to you uses the props when component mounted itself and when you update the state it does not reflect in the child component

to solve this you have to use the react life cycle hook called componentWillReceiveProps

like this

  componentWillReceiveProps() {
    this.drawChart();
  }

you also have to add this lifecycle hook in your component so that whenever your props get updated it recall the function and reflect the changes

Upvotes: 0

Ali Asgher Badshah
Ali Asgher Badshah

Reputation: 851

the state is changing the problem is that the setState is an async task so you have to wait till its end and to verify that your state is updating try the code below:-

dataForm = (
dataset,
canvascolor,
canvaswidth,
canvasheight,
barwidth,
barheight,
xspacing,
yspacing,
barcolor
) => {
this.setState({
  dataset,
  canvascolor,
  canvaswidth,
  canvasheight,
  barwidth,
  barheight,
  xspacing,
  yspacing,
  barcolor,
},()=>{
  console.log(this.state);
});

};

setState returns the callback after its finish its task you can use it to sync the setState function

to update the data in the child component when the state of the parent component changes you have to use the props like in below example

const ExampleComponent =(props) =>{
  return <div>
             <Text>{{props.text}}</Text>
         </div>
}

and pass the props in a component like this

<ExampleComponent text={this.state.text} />

now whenever you change the value of the text in the state it will reflect in your child component.

Upvotes: 1

Related Questions