I'm attempting to update a React.Component with data that can be changed by its parent. However, the component that is receiving the prop is not updating it state after receiving the prop.
For example, I have a parent function here called Edit. This creates its state to include a title field to be "". It then calls Firebase and lists out all the widgets that are in firebase for the user to see as buttons. Finally, when a user clicks on one of the widgets that was listed out, it then updates that title to be the value in firebase.
At the very bottom of this I have a component called WidgetManipulation. Here I'm attempting to pass this title down into the prop to be used. The title can change based on which of the buttons the user clicks to see more info on
export default function Edit() {
const [firebaseWidgetData, SetFirebaseWidgetData] = useState({
title: "",
});
const [dashboardWidgets, dashboardWidgetsLoading, dashboardWidgetsError] =
useCollection(collection(firestore, "Widgets"), {
snapshotListenOptions: { includeMetadataChanges: true },
});
const getWidgetInfoToEdit = async ({ target: { value } }) => {
const dashboardWidgetsDocRef = doc(firestore, "Widgets", value.toString());
const docSnap = await getDoc(dashboardWidgetsDocRef);
SetFirebaseWidgetData(JSON.parse(JSON.stringify(docSnap.data())));
}
return (
<div className={styles.container}>
{dashboardWidgetsError && (
<strong>Error: {JSON.stringify(error)}</strong>
)}
{dashboardWidgetsLoading && <span>Collection: Loading...</span>}
{dashboardWidgets && (
<span>
{dashboardWidgets.docs.map((doc) => (
<button key={doc.id} value={doc.id} onClick={getWidgetInfoToEdit}>
{`${doc.id} -
${doc.data().title}`}
</button>
))}
</span>
)}
<WidgetManipulation isCreate={false} firebaseWidgetData={firebaseWidgetData}></WidgetManipulation>
</div>
);
}
The main problem I'm running into is that the WidgetManipulation component does not ever receive an updated state value based on the firebaseWidgetData prop that I pass down.
My WidgetManipulation code is so:
export default class WidgetManipulation extends Component {
constructor(props) {
super(props);
this.state = {
widgetData: {
title: props.firebaseWidgetData.title,
}
}
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
id="title"
name="title"
type="text"
required
value={this.state.widgetData.title}
onChange={this.handleChange}
/>
<label htmlFor="title">Title</label>
</form>
)
}
}
Since it is a child component, you can just directly pass the prop variable inside the value attribute like this:
<input
id="title"
name="title"
type="text"
required
value={this.props.firebaseWidgetData.title}
onChange={this.handleChange}
/>
React will simply re-renders your parent component along with its child components, hence you won't need a state inside your child component. If you want your child component to have state based on the props you have passed, you can listen to the prop changes by using the componentDidMount and componentWillUnmount methods (if you are using React Class Component) and useEffect hook (if you are using React Functional Component).
EDIT:
Since the author wants the input field to be useable, we can simply pass the setFirebaseWidgetData. In your parent component, pass the setter function:
<WidgetManipulation isCreate={false} firebaseWidgetData={firebaseWidgetData} setFirebaseWidgetData={SetFirebaseWidgetData}></WidgetManipulation>
In the child component, simply use the setter function that we have passed.
export default class WidgetManipulation extends Component {
constructor(props) {
super(props);
this.state = {
widgetData: {
title: props.firebaseWidgetData.title,
}
}
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
id="title"
name="title"
type="text"
required
value={this.props.firebaseWidgetData.title}
onChange={(e) => {
this.props.setFirebaseWidgetData({
title: e.target.value
})
}}
/>
<label htmlFor="title">Title</label>
</form>
)
}
}