简体   繁体   中英

Child component not re-rendering with updated props

I have a child component, it looks through and creates Canvas elements in the DOM, then useEffect() draws things to these Canvases:

import { useEffect } from "react";

function Table(props) {
  console.log(">> In Table, props is ", props);

  useEffect(() => {
    console.log(">> in useEffect ");

    // prepare the data to render here and render to the multiple HTML Canvases in the DOM
  }, []);

  const options = [
    { value: 0, label: "1" },
    { value: 1, label: "2" }
  ];

  const onChannelXChange = (option) => {
    console.log("1. send the change back to the parent");

    let change = {
      type: "ChannelIndexChange",
      // TODO need to get the plot here
      plotIndex: 0,
      channel: "x",
      value: option.value,
    };
    props.parentCallback(change);
  };

  return (
    <table className="workspace">
      <tbody>
          <tr key={`tr-${fileIndex}`}>
            {props.workspaceState.plots.map((plot, plotIindex) => {
              return (
                <td key={`td-${plotIindex}`}>
                  <div>
                    <canvas
                      className="canvas"
                      id={`canvas-${fileIndex}-${plotIindex}`}
                      width="400"
                      height="400"
                    />
                    <Dropdown
                      options={options}
                      onChange={onChannelXChange}
                      placeholder="Select an option"
                    />
                  </div>
                </td>
              );
            })}
          </tr>
      </tbody>
    </table>
  );
}

export default Table;

And it's parent component:

import Table from "./Table";
import React, { useState } from "react";

class Workspace extends React.Component {
  constructor(props) {
    super();

    this.state = {
      workspaceState: {},
    };

    this.state.workspaceState = props.workspaceState;

    this.handleCallback = this.handleCallback.bind(this);
  }

  handleCallback = (option) => {
    this.props.workspaceState.value = option.value;

    // I expect this to re-render the Table Component with the updated props
    console.log("2. updating state");
    this.setState({ workspaceState: this.props.workspaceState });
  };

  render() {
    return (
      <Table
        enrichedEvents={this.props.enrichedEvents}
        workspaceState={this.props.workspaceState}
        className="workspace"
        parentCallback={this.handleCallback}
      ></Table>
    );
  }
}

export default Workspace;

When the suer clicks on the Dropdown, I pass the value back up to the parent component (Workspace). This then updates the Workspace state, and I then expect the child coponent to be re-rendered - except it is not. When I look at the logs, I see:

Workspace.js:44 1. send the change back to the parent
Workspace.js:44 2. updating parent state component
Table.js:95 >> props is {workspaceState: {...}}

But I dont see:

 >> in useEffect

I only see this log the first time the app runs. The Table component is indeed getting the new updated props, but it wont re-render with this new data. What am I doing wrong?

The component absolutely is re-rendering (because that's how react works), but execution of a useEffect hook is not coupled to each render of the associated component (in fact, that's its purpose).

You've provided useEffect(() => {}, [])

The second argument is the 'dependency' array. When this is empty, the useEffect callback will execute once only , straight after the component first mounts. If you want it to execute again when something changes, you'll need to include the changed value in the dependency array.

You generally only need to do this if you are going to trigger some kind of async behaviour using the changed value however. If 'prepare the data to render' does nothing but transform it into a useful format, you don't need a useEffect hook at all; that logic should go in the function body.

useEffect(() => {}, []) replace the componentDidMount in old react versions that means it execute only once after mounting the component in DOM. I am wondering if you really need a useEffect in your case, if it the case you need to use a useEffect without array of dependencies. LIke that:

 import { useEffect } from "react"; function Table(props) { console.log(">> In Table, props is ", props); useEffect(() => { console.log(">> in useEffect "); // prepare the data to render here }); const options = [ { value: 0, label: "1" }, { value: 1, label: "2" } ]; const onChannelXChange = (option) => { console.log("1. send the change back to the parent"); props.parentCallback(option); }; return ( <Dropdown options={options} onChange={onChannelXChange} placeholder="Select an option" /> ); } export default Table;

Solution 2: As i said am wondering if you really need a useEffect you can directly do it like that

 import { useEffect } from "react"; function Table(props) { console.log(">> In Table, props is ", props); // put you logic directly here // prepare the data to render here const options = [ { value: 0, label: "1" }, { value: 1, label: "2" } ]; const onChannelXChange = (option) => { console.log("1. send the change back to the parent"); props.parentCallback(option); }; return ( <Dropdown options={options} onChange={onChannelXChange} placeholder="Select an option" /> ); } export default Table;

Your effect has no dependencies, so it will run once, similar life-cycle method componentDidMount of a class component. If you want it to run whenever a part of your props change eg you need this as dependency.

  useEffect(() => {
    console.log(">> in useEffect ");

    // prepare the data to render here and render to the multiple HTML Canvases in the DOM
  }, [props.workspaceState]);

You should provide the workspaceState from state of parent component to your child component.

  <Table
    enrichedEvents={this.props.enrichedEvents}
    workspaceState={this.state.workspaceState}
    className="workspace"
    parentCallback={this.handleCallback}
  ></Table>

You are somehow trying to mutate the props of the parent component directly, I don't think this is ever advised. React will not recognize this and do a re-render.

  handleCallback = (option) => {
    this.props.workspaceState.value = option.value;

    // I expect this to re-render the Table Component with the updated props
    console.log("2. updating state");
    this.setState({ workspaceState: this.props.workspaceState });
  };

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