When I was working on a game project in React JS, I ran into an annoying error that said:
Error Code
'({ onPickWinner, canPick, timeToPick, hasWinner, revealMode }: PropsWithChildren<IPickWinnerProps>) => void' is not assignable to type 'FC<IPickWinnerProps>'.
At first, I didn’t understand what this meant. But after digging in react js, I figured out the root cause and I’ll walk you through it here step by step.
What I Was Trying to Do
I had a functional component called PickWinnerIdle
where I wanted to pass a few props and trigger a function when certain conditions were met. Here’s the interface I defined:
interface IPickWinnerProps {
canPick: boolean;
timeToPick: boolean;
revealMode: boolean;
hasWinner: boolean;
onPickWinner: () => void;
}
Then I wrote the component like this:
const PickWinnerIdle: React.FC<IPickWinnerProps> = ({
onPickWinner,
canPick,
timeToPick,
hasWinner,
revealMode
}) => {
const [pickWinnerLoading, setPickWinnerLoading] = useState(false);
setPickWinnerLoading(true); // Bad idea!
};
And boom, TypeScript yelled at me. The problem? My component wasn’t returning anything — not even <div></div>
— so TypeScript assumed it returns void
, which is not allowed for components typed as React.FC
.
What I Learn
React.FC
Must Return JSX
React functional components must return some JSX. Even if it’s just a placeholder, returning void
is not valid. So the fix was simple: return something.
Never Call setState
in Render
I was also calling setPickWinnerLoading(true)
directly in the function body. That’s a mistake. It causes infinite re-renders. Instead, use useEffect
.
The Fix Code
Here’s the corrected version of the component:
const PickWinnerIdle: FC<IPickWinnerProps> = ({
onPickWinner,
canPick,
timeToPick,
hasWinner,
revealMode
}) => {
const [pickWinnerLoading, setPickWinnerLoading] = useState(false);
useEffect(() => {
if (canPick && timeToPick && !hasWinner) {
setPickWinnerLoading(true);
onPickWinner();
}
}, [canPick, timeToPick, hasWinner, onPickWinner]);
return (
<div>
<h3>Pick Winner Status</h3>
<p>Can Pick: {canPick ? 'Yes' : 'No'}</p>
<p>Time to Pick: {timeToPick ? 'Yes' : 'No'}</p>
<p>Winner Revealed: {hasWinner ? 'Yes' : 'No'}</p>
<p>Reveal Mode: {revealMode ? 'Enabled' : 'Disabled'}</p>
<p>Loading: {pickWinnerLoading ? 'Loading...' : 'Idle'}</p>
<button disabled={!canPick || hasWinner} onClick={onPickWinner}>
Pick Winner
</button>
</div>
);
};
Practice Features I Added
To make it more interactive and helpful, I added some extras:
Timeout Simulation
I added a 2 second delay to simulate a network call using setTimeout
:
useEffect(() => {
if (pickWinnerLoading) {
const timer = setTimeout(() => {
setPickWinnerLoading(false);
}, 2000);
return () => clearTimeout(timer);
}
}, [pickWinnerLoading]);
Parent Component Example
<PickWinnerIdle
canPick={true}
timeToPick={true}
hasWinner={false}
revealMode={true}
onPickWinner={() => console.log("Winner picked")}
/>
Visual Feedback
{pickWinnerLoading && <span>Processing...</span>}
Final Thought
This little debugging session reminded me how TypeScript, though strict, is actually helping catch silly mistakes like forgetting a return
. The error message looked scary at first, but once I slowed down and understood the rules of React.FC
, it was an easy fix.