pmndrs / react-spring

✌️ A spring physics based React animation library
http://www.react-spring.dev/
MIT License
28.2k stars 1.19k forks source link

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

Open DamianPereira opened 3 years ago

DamianPereira commented 3 years ago

🐛 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

joshuaellis commented 3 years ago

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

DamianPereira commented 3 years ago

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

DerekLoop commented 3 years ago

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"],
  })
joshuaellis commented 3 years ago

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

DerekLoop commented 3 years ago

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

joshuaellis commented 3 years ago

@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.

DerekLoop commented 3 years ago

@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.

joshuaellis commented 3 years ago

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?

DerekLoop commented 3 years ago

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.