Ryan Florence doesn't like me saying that, but it's true.
Why is it true? Well, they look like render props until you scroll down to the return statement and see an arrow function. If you refactor a stateless component into a stateful component (i.e. add a setup scope), the render props subtly become setup props, right under your nose.
Most of the time, using setup props is dangerous and unnecessary, as it leads to problems down the road. At first, all is well, but then you use the component more dynamically and, BOOM, you've got a stale data issue that presents itself in confusing ways. Of course, this problem is most apparent in more complex applications with many moving parts.
More importantly, can it be avoided without sacrificing the aesthetic appeal of Remix 3's component design? Actually, yes! At least, that's my opinion.
// Before (current API)
function MyComponent(this: Remix.Handle<Context>, props: Props) {
props; // Props are fresh during setup. Captured by default.
return (props: Props) => {
props; // Props are fresh during render. Optionally captured.
};
}
// After (proposed API)
function MyComponent(this: Remix.Handle<Props, Context>) {
this.props; // Props are always fresh. Optionally captured.
// Capture props when necessary.
const { defaultValue } = this.props;
return (props: Props) => {
props; // Props are fresh during render. Optionally captured.
this.props; // Props are always fresh. Optionally captured.
};
}Notice how, in the new version, all references to props or this.props have the “Optionally captured” description. This is the key difference. Staleness is never the default. It's always something you have to opt into.
Additionally, when you need to hoist shared state up to a parent component that was once a stateless layout component, there's no longer a footgun that results in stale props.
TypeScript will nudge you to refactor your component such that any existing props references still point to the latest props, as you were doing before the refactor. This encourages the developer to refactor their component, not by wrapping the JSX return expression in an arrow function, but by wrapping the stateless component in a stateful component. This point is subtle, but important.