Skip to content

Commit

Permalink
Add StrictAnimatedProps to fix missing Animated types (#46659)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #46659

The current `AnimatedProps` maps all types to `any` which prevents us from catching many types of issues. Introduce an alternative variant which we can adopt incrementally which enforces the underlying types.

Changelog: [General][Fixed] Improved types for AnimatedProps

Reviewed By: SamChou19815

Differential Revision: D63315760

fbshipit-source-id: 718fc2754cc99891e490a48d7792f626205c37e4
  • Loading branch information
javache authored and facebook-github-bot committed Oct 1, 2024
1 parent 39c98fb commit 390925e
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 33 deletions.
78 changes: 46 additions & 32 deletions packages/react-native/Libraries/Animated/createAnimatedComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,24 @@ export type AnimatedProps<Props: {...}> = {
}>)]: any,
};

// We could use a mapped type here to introduce acceptable Animated variants
// of properties, instead of doing so in the core StyleSheetTypes
// Inexact Props are not supported, they'll be made exact here.
export type StrictAnimatedProps<Props: {...}> = $ReadOnly<{
...$Exact<Props>,
passthroughAnimatedPropExplicitValues?: ?Props,
}>;

export type AnimatedComponentType<
Props: {...},
+Instance = mixed,
> = React.AbstractComponent<AnimatedProps<Props>, Instance>;

export type StrictAnimatedComponentType<
Props: {...},
+Instance = mixed,
> = React.AbstractComponent<StrictAnimatedProps<Props>, Instance>;

export default function createAnimatedComponent<TProps: {...}, TInstance>(
Component: React.AbstractComponent<TProps, TInstance>,
): AnimatedComponentType<TProps, TInstance> {
Expand All @@ -44,40 +57,41 @@ export function unstable_createAnimatedComponentWithAllowlist<
>(
Component: React.AbstractComponent<TProps, TInstance>,
allowlist: ?AnimatedPropsAllowlist,
): AnimatedComponentType<TProps, TInstance> {
const AnimatedComponent = React.forwardRef<AnimatedProps<TProps>, TInstance>(
(props, forwardedRef) => {
const [reducedProps, callbackRef] = useAnimatedProps<TProps, TInstance>(
// $FlowFixMe[incompatible-call]
props,
allowlist,
);
const ref = useMergeRefs<TInstance>(callbackRef, forwardedRef);
): StrictAnimatedComponentType<TProps, TInstance> {
const AnimatedComponent = React.forwardRef<
StrictAnimatedProps<TProps>,
TInstance,
>((props, forwardedRef) => {
const [reducedProps, callbackRef] = useAnimatedProps<TProps, TInstance>(
// $FlowFixMe[incompatible-call]
props,
allowlist,
);
const ref = useMergeRefs<TInstance>(callbackRef, forwardedRef);

// Some components require explicit passthrough values for animation
// to work properly. For example, if an animated component is
// transformed and Pressable, onPress will not work after transform
// without these passthrough values.
// $FlowFixMe[prop-missing]
const {passthroughAnimatedPropExplicitValues, style} = reducedProps;
const passthroughStyle = passthroughAnimatedPropExplicitValues?.style;
const mergedStyle = useMemo(
() => composeStyles(style, passthroughStyle),
[passthroughStyle, style],
);
// Some components require explicit passthrough values for animation
// to work properly. For example, if an animated component is
// transformed and Pressable, onPress will not work after transform
// without these passthrough values.
// $FlowFixMe[prop-missing]
const {passthroughAnimatedPropExplicitValues, style} = reducedProps;
const passthroughStyle = passthroughAnimatedPropExplicitValues?.style;
const mergedStyle = useMemo(
() => composeStyles(style, passthroughStyle),
[passthroughStyle, style],
);

// NOTE: It is important that `passthroughAnimatedPropExplicitValues` is
// spread after `reducedProps` but before `style`.
return (
<Component
{...reducedProps}
{...passthroughAnimatedPropExplicitValues}
style={mergedStyle}
ref={ref}
/>
);
},
);
// NOTE: It is important that `passthroughAnimatedPropExplicitValues` is
// spread after `reducedProps` but before `style`.
return (
<Component
{...reducedProps}
{...passthroughAnimatedPropExplicitValues}
style={mergedStyle}
ref={ref}
/>
);
});

AnimatedComponent.displayName = `Animated(${
Component.displayName || 'Anonymous'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,10 +721,18 @@ exports[`public API should not change unintentionally Libraries/Animated/createA
>,
}>)]: any,
};
export type StrictAnimatedProps<Props: { ... }> = $ReadOnly<{
...$Exact<Props>,
passthroughAnimatedPropExplicitValues?: ?Props,
}>;
export type AnimatedComponentType<
Props: { ... },
+Instance = mixed,
> = React.AbstractComponent<AnimatedProps<Props>, Instance>;
export type StrictAnimatedComponentType<
Props: { ... },
+Instance = mixed,
> = React.AbstractComponent<StrictAnimatedProps<Props>, Instance>;
declare export default function createAnimatedComponent<
TProps: { ... },
TInstance,
Expand All @@ -737,7 +745,7 @@ declare export function unstable_createAnimatedComponentWithAllowlist<
>(
Component: React.AbstractComponent<TProps, TInstance>,
allowlist: ?AnimatedPropsAllowlist
): AnimatedComponentType<TProps, TInstance>;
): StrictAnimatedComponentType<TProps, TInstance>;
"
`;

Expand Down

0 comments on commit 390925e

Please sign in to comment.