react
react copied to clipboard
No good alternative once this happens "Support for defaultProps will be removed from function components in a future major release"
React version: 18.3.1
Deprecation Warning:
Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.
While the suggestion to use JS default params like const Drawer = ({isOpen = true, showHandle = true}) => {} seems like an excellent syntactic improvement, this only works for the package maintainer.
Having the defaultProps as a static property enables configurability, where the application developer may be allowed to set the defaultProps for their own application, whereas javascript default parameters are set once by the package maintainer and never allowed to update.
This comes in to play most in the excellent recharts package, where props are passed to set the display of the charts. defaultProps are used to avoid these 3 props from being set on each instance of the <Line/> component.
Line.defaultProps = {
...Line.defaultProps,
isAnimationActive: false,
stroke: PRIMARY,
strokeWidth: 2,
};
defaultProps saves hundreds of lines of code!
The alternatives are UGLY and I would not recommend them, I very much prefer the clean defaultProps way instead. In recharts what you have to do now is:
const myLine = (props) => (
<Line strokeWidth={2} stroke={PRIMARY} isAnimationActive={false} {...props}/>
);
Ultimately the react team may have been too quick to dismiss defaultProps.
Doesn't this work
const LineDefaultProps = {
isAnimationActive: false,
stroke: PRIMARY,
strokeWidth: 2,
}
const myLine = (props) => (
<Line {...LineDefaultProps} {...props} />
)
Good use of spread operator but no, that only applies to the instance of Line
Good use of spread operator but no, that only applies to the instance of Line
The idea would be that you'd use MyLine throughout your codebase.
You'd gain more explicitness in your codebase and developers wouldn't wonder where some default props are coming from. You're also less likely to encounter obscure code splitting bugs where some chunk uses Line but the module that mutates Line.defaultProps isn't loaded yet.
Closing because that's the recommended alternative and we're not adding back defaultProps.
You'd gain more explicitness in your codebase
I think it is not explicitness but useless verboseness. Just see the code:
const MyImg = styled.img`
position: absolute;
left: 0;
top: 0;
width: 200px;
height: auto;
`
// Example with default props
MyImg.defaultProps = { src: nextUp }
// Example with component wrapping
const MyImgWithSrc = React.forwardRef<HTMLImageElement, React.ComponentPropsWithoutRef<'img'>>(
(props, ref) => <MyImg ref={ref} {...props} src={nextUp} />
)
And i just CANNOT understand how to properly type generic wrapper component that sets the "default" props:
// Don't know how to type ref
const withDefaults =
<FC extends React.FC<P>, P extends object>
(defaultProps: NoInfer<PropsWithoutRef<P>>, Component: FC): FC =>
React.forwardRef<React.ElementRef<FC>, React.ComponentPropsWithoutRef<P>>(
(props, forwardedRef) => {
return <Component {...defaultProps} {...props} ref={forwardedRef} />
}
)
It feels weird I'm getting this warning because I'm using await in a async function 😅 Pretty sure that shouldn't be giving a warning?
It's not even a react functional component, just a utility function for my Jest tests.
I have a hard time believing it's that line of mine that's the problem. It has to be the actual code that's being tested. It would be very good if these warnings actually specified which defaultProp that's being used. Otherwise it's going to a real pain of a detective job to try and patch these. In general I believe we always use functional parameters, so finding where these defaultProps are will be tough.
Edit: After some more research, it all seems to boil down to my tests testing our async-select, so most likely the problem lies within React-Select. Hoping it'll be solved after I update the dependency.
Doesn't this work
const LineDefaultProps = { isAnimationActive: false, stroke: PRIMARY, strokeWidth: 2, } const myLine = (props) => ( <Line {...LineDefaultProps} {...props} /> )
I would like to call out an edge case with this solution if a prop is explicitly set to undefined.
({ ...{ foo: 'hello' }, ...{ foo: undefined } }) // -> { foo: undefined }
This would not behave like defaultProps currently does and could cause a hard to notice bug, especially if you use a codemod or something to adopt this pattern at scale.
Not suggesting that we keep defaultProps and I'm glad to hear "we're not adding back defaultProps" but just wanted to call that out. I think the defaultProps deprecation is much more disruptive than the upgrade guide makes it seem.
Seems like there is a confusion.
Removing Line.defaultProps requires change only to the definition of the Line component, not every instance.
I upgraded my project recently and took only few minutes to make that change (don't use TS though, so not sure how that is affected)
Yes, is boring that the React Team deletes legacy code, why just don't leave it there to avoid breaking changes? Migrating and starting fixing dependencies issues after the migration is a pain, and now the React team also breaks our apps, just to save a couple of lines in their code, I have many apps with thousands of code, and is not feasible to re-write all with the new standard, more when the old code is stable and working well.
What I did to fix this annoying issue was in each functional component.. add the next line
export function DropDown(props){
props = {...DropDown.defaultProps, ...props}. <--- add this line at the beginning of your func comp.
}
DropDown.defaultProps = { selected:null, styles:null, options:null, placeholder:'Select', callback:null, disabled:false... }
I hope they never decide to remove or kill class Component support, that will be a real mistake... Please React Team, think in backward-compatibility
Now, with React 19 eliminating the need of React.forwardRef 🎉🎉🎉, it is possible somehow.
But builtin type Partial<T> allows any prop of any value outside of keyof T (is it TS bug?) and i don't know what to do with it.
My solution:
export type PartialDefaults<O extends object = object, Defaults extends Partial<O> = Partial<O>> =
& Omit<O, keyof Defaults>
& { [DProp in keyof Defaults & keyof O]?: O[DProp] }
export const withDefaults = <
P extends object = object,
DefaultP extends Partial<P> = Partial<P>,
// Provided default props become optional
OutP extends PartialDefaults<P, DefaultP> = PartialDefaults<P, DefaultP>,
>(
Component: React.FC<P>, defaultProps: DefaultP,
): React.FC<OutP> => {
// ⚠️ Need 'as any' because builtin Partial<T> allows
// any prop of any value outside of keyof T (is it TS bug?)
return (props: OutP) => <Component {...(defaultProps as any)} {...props} />
}
const MyImg = styled.img`
position: absolute;
left: 0;
top: 0;
width: 200px;
height: auto;
`
const MyComponent = (props: {
count: number
text: string
hidden?: boolean | undefined
isError?: boolean | undefined
}) => <></>
// ✅ Correct
const MyComponentWithDefaults = withDefaults(MyComponent, { text: 'text', isError: true })
// ❌ Must be error but it is correct
const _MyComponentWithDefaults = withDefaults(MyComponent, { text: 'text', isError: true, a: 1 })
// ⚠️ builtin Partial<T> allows any prop of any value outside of keyof T (is it TS bug?)
/*
Not works:
type Partial<T extends object> = {
[Prop in string]?: Prop extends keyof T ? T[Prop] : never
}
Not supported:
type Partial<T> = {
[P in keyof T]?: T[P]
[...Rest in string]: never
}
*/
// ✅ Correct ⚠️ Error - TS2322: Type number is not assignable to type string
const __MyComponentWithDefaults = withDefaults(MyComponent, { text: 1, isError: true })
const UsageExample = () => {
return (
<>
{/* ✅ Correct */}
<MyImg />
{/* ✅ Correct */}
<MyImgWithSrc src={isagi} alt="Isagi" />
{/* ✅ Correct ⚠️ Error - Property count is missing in type {} but required in type */}
<MyComponentWithDefaults />
{/* ✅ Correct ⚠️ Error - TS2322: Type string is not assignable to type number */}
<MyComponentWithDefaults count="one" hidden />
{/* ✅ Correct */}
<MyComponentWithDefaults count={1} />
{/* ✅ Correct */}
<MyComponentWithDefaults count={1} hidden />
{/* ✅ Correct */}
<MyComponentWithDefaults count={1} text="text" isError={false} />
</>
)
}