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?
CodePudding user response:
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.
CodePudding user response:
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;
CodePudding user response:
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 e.g. 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 });
};