Reputation: 3880
I want to use a callback in a child react component so that I can invoke a function from my child component in my parent component. However, when I handle the callback in my parent component, and try to set this child's function so that I can invoke this function later on when a button gets clicked, this function ends up getting invoked unexpectedly.
What I want to happen is that the sampleFunction
to be invoked when the 'Invoke Sample Function' button is clicked, but instead, sampleFunction
is invoked when the parent component is mounted (and the console is thus logged with 'foo'). How can I properly pass this callback function from the child to the parent? Thanks.
index.js:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
useEffect(() => {
callback({
toggleStatus,
sampleFunction: () => console.log("foo")
});
}, [callback, toggleStatus]);
const handleClick = () => {
setToggleStatus(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(null);
const handleCallback = props => {
setFn(props.sampleFunction);
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Upvotes: 0
Views: 534
Reputation: 3880
Thanks to @StackedQ and @Anthony for the help. As Anthony mentioned, in order to avoid an endless loop, I had to add an extra condition to handleCallback
:
index.js:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
useEffect(() => {
callback({
toggleStatus,
sampleFunction: () => () => setToggleStatus(prevState => !prevState)
});
}, [callback, toggleStatus]);
const handleClick = () => {
setToggleStatus(prevState => !prevState);
};
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(null);
const handleCallback = props => {
if (!fn) {
setFn(props.sampleFunction);
}
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Upvotes: 0
Reputation: 3339
You need to update your code as you are calling the callback in the initialization of your child component.
useEffect(() => {
// remove below call
callback({
toggleStatus,
sampleFunction: () => console.log("foo")
});
}, [callback, toggleStatus]);
Upvotes: 0
Reputation: 4139
Based on what this question covers and the usage you want, you should use useCallback
(docs - more info) instead of useEffect
. something like the following is kind of better implementation:
import React from "react";
import ReactDOM from "react-dom";
const Component = ({ callback }) => {
const [toggleStatus, setToggleStatus] = React.useState(false);
const handleClick = React.useCallback(() => {
setToggleStatus(prevState => !prevState);
callback({
toggleStatus,
sampleFunction: () => () => console.log("foo")
})
}, [callback, toggleStatus])
return (
<>
<button type="button" onClick={handleClick}>
Click Me
</button>
<h1>{toggleStatus ? "On" : "Off"}</h1>
</>
);
};
const App = () => {
const [fn, setFn] = React.useState(() => null);
const handleCallback = props => {
console.log(props)
setFn(props.sampleFunction);
};
return (
<>
<Component callback={handleCallback} />
<button type="button" onClick={fn}>
Invoke Sample Function
</button>
</>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Upvotes: 1