Motion: [BUG] motion.custom should accept type parameters

Created on 22 Aug 2019  Â·  3Comments  Â·  Source: framer/motion

The motion.custom component should accept type parameters for passing the custom component props.

Having the following base component:

import styled from '@emotion/styled'
import * as system from 'styled-system'

export type BoxProps = system.BackgroundColorProps &
  system.BorderProps &
  system.FlexboxProps &
  system.LayoutProps &
  system.OpacityProps &
  system.SpaceProps

export default styled.div<BoxProps>`
  ${system.compose(
    system.border,
    system.color,
    system.flexbox,
    system.layout,
    system.space
  )};

  box-sizing: border-box;
`

Wrapping the custom component with motion.custom:

import Box from 'components/box'

const Container = motion.custom(Box)

And using the motion wrapped component passing my custom component original props:

<Container
  minHeight={viewportHeight}
  display="flex"
  flexDirection="column"
  alignItems="center"
  justifyContent="center"
  opacity={viewportHeight === 0 ? 0 : 1}
  p={2}
>
  Test
</Container>

Gives the following TypeScript error:

Type '{ children: (string | number | boolean | {} | ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<...>)> | ... 4 more ... | undefined)[]; ... 6 more ...; p: number; }' is not assignable to type 'IntrinsicAttributes & MotionProps & RefAttributes<Element>'.
  Property 'children' does not exist on type 'IntrinsicAttributes & MotionProps & RefAttributes<Element>'.ts(2322)

So my solution is to just accept a generic type parameter in motion.custom to avoid this errors:

import Box, { BoxProps } from 'components/box'

const Container = motion.custom<BoxProps>(Box)
bug

Most helpful comment

Ran into this issue too and I believe a good fix could be if the type ends up as this:

// framer-motion.d.ts — line: 1953

custom: <Props = {}>(Component: ComponentType<Props>) => ForwardRefExoticComponent<MotionProps & RefAttributes<Element> & Omit<ComponentProps<typeof Component>, keyof MotionProps> & { children: ReactNode }>;

Highlights:

  • Infers the props of the passed component
  • Makes sure the motion prop definitions take precedence over the inferred props
  • Adds children type

This would need no further changes to the motion.custom API and would _just work_.

I'll try a PR later but have no time at the moment to debug why the bootstrap command is failing on my mac.

I think the line changes needed should be around here:

// motion/index.tsx — line: 64
export const motion = {
    custom: (Component: ComponentType<any>) => {
        return createMotionComponent(createDomMotionConfig(Component))
    },
    ...htmlMotionComponents,
    ...svgMotionComponents,
}
//
// replace custom with this function:
//
function custom<P = {}>(Component: ComponentType<P>) { return createMotionComponent(createDomMotionConfig(Component)) }

Haven't tested it, but it must be along those lines.


In the meantime here's a utility function animated that implements it:

//
// Utility to create a type-safe motion.custom
//
// Example:
//
// const AnimatedBox = animated(Box)
//

export function animated<Props = {}>(Component: ComponentType<Props>) {
    return motion.custom(Component) as ForwardRefExoticComponent<
        MotionProps &
            RefAttributes<Element> &
            OmitMotionProps<ComponentProps<typeof Component>> & {
                children: ReactNode
            }
    >
}

type OmitMotionProps<Props> = Omit<Props, keyof MotionProps>

You can then wrap components easily and with type safety:

const AnimatedBox = animated(Box)

// the above will have both the MotionProps and the BoxProps
// the former takes precedence over the latter

/cc @henriquea

All 3 comments

Ran into this issue too and I believe a good fix could be if the type ends up as this:

// framer-motion.d.ts — line: 1953

custom: <Props = {}>(Component: ComponentType<Props>) => ForwardRefExoticComponent<MotionProps & RefAttributes<Element> & Omit<ComponentProps<typeof Component>, keyof MotionProps> & { children: ReactNode }>;

Highlights:

  • Infers the props of the passed component
  • Makes sure the motion prop definitions take precedence over the inferred props
  • Adds children type

This would need no further changes to the motion.custom API and would _just work_.

I'll try a PR later but have no time at the moment to debug why the bootstrap command is failing on my mac.

I think the line changes needed should be around here:

// motion/index.tsx — line: 64
export const motion = {
    custom: (Component: ComponentType<any>) => {
        return createMotionComponent(createDomMotionConfig(Component))
    },
    ...htmlMotionComponents,
    ...svgMotionComponents,
}
//
// replace custom with this function:
//
function custom<P = {}>(Component: ComponentType<P>) { return createMotionComponent(createDomMotionConfig(Component)) }

Haven't tested it, but it must be along those lines.


In the meantime here's a utility function animated that implements it:

//
// Utility to create a type-safe motion.custom
//
// Example:
//
// const AnimatedBox = animated(Box)
//

export function animated<Props = {}>(Component: ComponentType<Props>) {
    return motion.custom(Component) as ForwardRefExoticComponent<
        MotionProps &
            RefAttributes<Element> &
            OmitMotionProps<ComponentProps<typeof Component>> & {
                children: ReactNode
            }
    >
}

type OmitMotionProps<Props> = Omit<Props, keyof MotionProps>

You can then wrap components easily and with type safety:

const AnimatedBox = animated(Box)

// the above will have both the MotionProps and the BoxProps
// the former takes precedence over the latter

/cc @henriquea

@petecorreia Thanks for taking a proper look at this and for the clear explanation. I'm going to take a stab at this along the lines you describe.

//
// Utility to create a type-safe motion.custom
//
// Example:
//
// const AnimatedBox = animated(Box)
//

export function animated<Props = {}>(Component: ComponentType<Props>) {
  return motion.custom(Component) as ForwardRefExoticComponent<
      MotionProps &
          RefAttributes<Element> &
          OmitMotionProps<ComponentProps<typeof Component>> & {
              children: ReactNode
          }
  >
}

type OmitMotionProps<Props> = Omit<Props, keyof MotionProps>

the utility function you shared isn't working for me, can you please share the full code

Was this page helpful?
0 / 5 - 0 ratings