react-spring icon indicating copy to clipboard operation
react-spring copied to clipboard

Typescript: Assigning useTransition return value to a prop with type CSSProperties results in 'not assignable' error

Open DamianPereira opened this issue 3 years ago • 9 comments

🐛 Bug Report

The returned value from useTransition styles is not assignable to a prop with type CSSProperties. The full error is:

TS2322: Type '{ opacity: SpringValue<number>; }' is not assignable to type 'CSSProperties'.   Types of property 'opacity' are incompatible.     Type 'SpringValue<number>' is not assignable to type 'Opacity | undefined'.       Type 'SpringValue<number>' is not assignable to type 'number & {}'.         Type 'SpringValue<number>' is not assignable to type 'number'. 

This is probably related to https://github.com/pmndrs/react-spring/issues/1102, it seems to keep happening under this scenario.

To Reproduce

Steps to reproduce the behavior: Using this code (or any component which receives styles as a prop with type CSSProperties):

const AnimatedTest = ({ styles }: { styles: CSSProperties }) => (
  <animated.div style={styles}>Test</animated.div>
);

export default function App() {
  const [items, setItems] = useState([]);
  const transitions = useTransition(items, {
    from: { opacity: 0 },
    enter: { opacity: 1 },
    leave: { opacity: 0 },
    delay: 200,
  });

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      {transitions((styles) => <AnimatedTest styles={styles}/>)}
    </div>
  );
}

Expected behavior

There should be no compilation error since the type returned from useTransition should be compatible with CSSProperties.

Link to repro (highly encouraged)

Codesandbox link

Environment

  • react-spring v9.2.4
  • react v17.0.3

DamianPereira avatar Jul 23 '21 23:07 DamianPereira

Please create a reproduction of the issue in a sandbox for review.

joshuaellis avatar Aug 04 '21 21:08 joshuaellis

I'm sorry, the link was wrong, I meant to link to this sandbox, which has the issue.

DamianPereira avatar Aug 05 '21 15:08 DamianPereira

This workaround helped me in the meantime

  const dropdown = useSpring({
    opacity: (showMenu ? 1 : 0) as React.CSSProperties["opacity"],
    pointerEvents: (showMenu
      ? "all"
      : "none") as React.CSSProperties["pointerEvents"],
  })

DerekLoop avatar Oct 20 '21 17:10 DerekLoop

Should it be assignable to CSSProperties? You can resolve this by passing SpringValues type. See https://codesandbox.io/s/react-spring-type-bug-forked-netbu?file=/src/App.tsx

joshuaellis avatar Oct 21 '21 07:10 joshuaellis

Thanks @joshuaellis that looks like an even-cleaner solution.

DerekLoop avatar Oct 21 '21 16:10 DerekLoop

@DamianPereira it'd be good to hear your thoughts on why you think it's an issue since we're not passing CSSProperties, we're passing SpringValues.

joshuaellis avatar Oct 21 '21 16:10 joshuaellis

@joshuaellis My 2 cents are that it should work without needing to cast or specify the type, which would mean returning the correct SpringValues by default 🙂 But this may be challenging to implement.

DerekLoop avatar Oct 21 '21 18:10 DerekLoop

It does return SpringValues by default. The original issue is demonstrating that passing the props to another component means you have to cast it with SpringValues not CSSProperties. Unless you have a separate issue @DerekLoop?

joshuaellis avatar Oct 21 '21 19:10 joshuaellis

Hi @joshuaellis ! Thanks for the help. Maybe I'm just not understanding the types correctly.

This example works in TypeScript:

  import { animated as a, SpringValues, useSpring } from "@react-spring/web"

  const dropdown = useSpring({
    opacity: (showMenu ? 1 : 0) as React.CSSProperties["opacity"],
    pointerEvents: (showMenu
      ? "all"
      : "none") as React.CSSProperties["pointerEvents"],
  })

  return (<a.div style={dropdown}>Content!</a.div>)

This example gives an error:

  import { animated as a, SpringValues, useSpring } from "@react-spring/web"

  const dropdown = useSpring({
    opacity: showMenu ? 1 : 0,
    pointerEvents: showMenu ? "all" : "none",
  })

  return (<a.div style={dropdown}>Content!</a.div>)

This is the error:

Type '{ opacity: SpringValue<number>; pointerEvents: SpringValue<string>; }' is not assignable to type '{ alignContent?: "initial" | "end" | (string & {}) | "baseline" | "inherit" | "start" | "center" | "-moz-initial" | "revert" | "unset" | "normal" | "space-around" | "space-between" | ... 5 more ... | undefined; ... 802 more ...; matrix3d?: AnimatedObject<...> | undefined; }'.
  Types of property 'pointerEvents' are incompatible.
    Type 'SpringValue<string>' is not assignable to type '"initial" | "all" | "none" | "fill" | "stroke" | "auto" | "inherit" | "-moz-initial" | "revert" | "unset" | "painted" | "visible" | "visibleFill" | "visiblePainted" | "visibleStroke" | FluidValue<...> | undefined'.
      Type 'SpringValue<string>' is not assignable to type 'FluidValue<NonObject<PointerEvents | undefined>, any>'.
        The types returned by 'get()' are incompatible between these types.
          Type 'string' is not assignable to type 'NonObject<PointerEvents | undefined>'.ts(2322)
index.d.ts(1841, 9): The expected type comes from property 'style' which is declared here on type 'IntrinsicAttributes & AnimatedProps<{ onChange?: FormEventHandler<HTMLDivElement> | undefined; onPause?: ReactEventHandler<HTMLDivElement> | undefined; ... 253 more ...; ref?: RefObject<...> | ... 2 more ... | undefined; }> & { ...; }'
(JSX attribute) style?: {
    alignContent?: "end" | (string & {}) | "baseline" | "inherit" | "initial" | "start" | "center" | "-moz-initial" | "revert" | "unset" | "normal" | "space-around" | "space-between" | ... 5 more ... | undefined;
    ... 802 more ...;
    matrix3d?: AnimatedObject<...> | undefined;
} | undefined

This example also works without an error:

  import { animated as a, SpringValues, useSpring } from "@react-spring/web"

  const dropdown = useSpring({
    opacity: showMenu ? 1 : 0,
    pointerEvents: showMenu ? "all" : "none",
  }) as SpringValues<{ opacity: number; pointerEvents: "all" | "none" }>


  return (<a.div style={dropdown}>Content!</a.div>)

I'm just not sure why casting is necessary in either case 🎃😅 Though the SpringValues solution seems more correct.

DerekLoop avatar Oct 21 '21 19:10 DerekLoop