Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I spread props to a React component that uses exact props when using Flow?

Consider the following example using flow props.

import * as React from 'react';

type FooProps = {| 
  foo: number,
  bar?: string 
|};

class Foo extends React.Component<FooProps> {}

We have React component class Foo that accept an exact object of props. We want to enforce exactness so that users are not inadvertently making typos on their props (e.g. baz when they meant to use bar).

This works absolutely fine and errors happen as expected.

However, what if we want to spread props to this component from somewhere else? For example:

const f = (props: FooProps) => <Foo {...props} />;

Flow will give us an error about exactness on props:

10: const f = (props: FooProps) => <Foo {...props} />;
                                    ^ Cannot create `Foo` element because inexact props [1] is incompatible with exact `FooProps` [2].
References:
10: const f = (props: FooProps) => <Foo {...props} />;
                                   ^ [1]
8: class Foo extends React.Component<FooProps> {}
                                     ^ [2]

Disregarding the argument, "you shouldn't spread props to components like that when you're asking for exactness", how can spreading be achieved?

I did find one way to do this, but it uses an undocumented utility type $Shape<T> (code). It's unclear if this has any consequences or side effects, but it appears to work correctly:

class Foo extends React.Component<$Shape<FooProps>> {}

Here's a link to try out what I've got using $Shape<T> with items I expect to error (and not):

like image 347
Paul Armstrong Avatar asked Feb 17 '18 15:02

Paul Armstrong


People also ask

How do you send props to components in React?

export default App; Basically that's how props are passed from component to component in React. As you may have noticed, props are only passed from top to bottom in React application's component hierarchy. There is no way to pass props up to a parent component from a child component.

How do you pass props to a component passed as prop?

You can pass a component as props in React by using the built-in children prop. All elements you pass between the opening and closing tags of a component get assigned to the children prop.

How do you pass props from child to parent component?

To pass data from a child component to its parent, we can call a parent function from the child component with arguments. The parent function can be passed down to the child as a prop, and the function arguments are the data that the parent will receive.

How can you avoid prop drilling?

Lift state up. Lifting the state up in the component tree can help minimize prop drilling. You remove the need to pass more props down to child components by lifting the state that depends on those props to the parent. It also makes the state more flexible and easier to share between multiple child components.


1 Answers

Apologies if this is not from an official source.

Solution: Type cast props to any before spread.

It meets your requirements of spread within a function. The function ensures exactness prior to type casting.

NB: $Shape<FooProps> on the cast does not work, it still thinks its exact. Neither does assigning props to a const spreadable: $Shape<FooProps> = props It seems $Shape doesn't remove the exactness, despite appearances in your example. One way or another, you have to strip the exactness, and doing it internally the function seems plausible.

The Flow docs discuss type casting via any as legitimate although potential unsafe and not recommended (because you loose type safety). I think in the context its reasonable.

import * as React from 'react';

type FooProps = {| 
  foo: number,
  bar?: string 
|};

class Foo extends React.Component<FooProps> {}

const f = (props: FooProps) => <Foo {...(props: any)} />;

f({foo: 1})                     // PASS: with foo, without bar
f({foo: 1, bar: ''})            // PASS: with foo and bar
{ <Foo foo={1} /> }             // PASS: with foo, without bar
{ <Foo foo={1} bar="" /> }      // PASS: with foo and bar

f({})                           // FAIL: missing foo
f({foo: ''})                    // FAIL: wrong type of foo
f({foo: 1, bar: 1})             // FAIL: with foo, wrong type of bar
f({foo: 1, x: 1})               // FAIL: unexpected x
{ <Foo /> }                     // FAIL: missing foo
{ <Foo foo="" /> }              // FAIL: wrong type of foo
{ <Foo foo={1} bar={1} /> }     // FAIL: with foo, wrong type of bar
{ <Foo foo={1} x={1} /> }       // FAIL: unexpected x

Try it here

like image 124
Dave Meehan Avatar answered Sep 21 '22 01:09

Dave Meehan