MarkeD
MarkeD

Reputation: 2631

How to pass React Component prop to child function using {children}?

I'm new to React so this may be something obvious but I can't pass through a prop to a function that creates Components from its parent.

I can pass props to child components, but the same doesn't work for functions.

I have <Subscription> which I can pass through arguments like so, from its parent post:

<Subscription auth={auth} stripeAmount={post.amount} stripePlanId={post.planid}/>

This creates a Stripe subscription. I want to limit the subscription to subscribe to the stripePlanId which I do so via:

class Subscription extends React.Component {

  // https://stripe.com/docs/checkout#integration-custom
  componentDidMount() {
    this.stripeCheckout = window.StripeCheckout.configure({
      ...etc...
      email: this.props.auth.email,
    })
  }

  newSubscription = () => {
    var stripePlanId = this.props.stripePlanId;
    this.stripeCheckout.open({
      amount: this.props.stripeAmount, // in cents
      description: this.props.stripePlanId,
      token: function(token){
        createSubscription(token, stripePlanId)
      }
    })
  }

 ..etc..

This works great. But now to pass through the stripePlanId I can't find out how to pass the stripePlanId through since it renders via a function - this {children} argument seems to only pass in the function, and attempting to add arguments causes errors that they are not functions it expects to act upon the arguments passed:

const FireflySubscription = ({children}) => (
  <FirebaseAuth>
    { ({isLoading, error, auth}) => {
      if (error || isLoading || !auth) {
        //it pushes these arguments to the parent function
        return children({ 
          error,
          isLoading,
          subscription: null,
        })
      }

      // the issue - how to populate this?
      const stripePlanId = ""  

      // when working this should return the subscription for only this planId
      if (stripePlanId) {
        return <FirestoreCollection
        path="subscriptions"
        filter={[['createdBy', '==', auth.uid], ['stripePlanId','==', stripePlanId]]}
      >
        { ({error, isLoading, data}) => {
          return children({
            error,
            isLoading,
            subscription: data.length > 0 ? data : null,
          })
        }}
      </FirestoreCollection>

      }

      return <FirestoreCollection
        path="subscriptions"
        filter={['createdBy', '==', auth.uid]}
      >
        { ({error, isLoading, data}) => {
          return children({
            error,
            isLoading,
            subscription: data.length > 0 ? data : null,
          })
        }}
      </FirestoreCollection>

    }}
  </FirebaseAuth>
)

export default FireflySubscription

I have tried to pass it through with another method, but the "scope" does not pass through:

getPostSubscriptions = stripePlanId => {
    return <FireflySubscription>
// it gets these arguments from FireflySubscription function above
    { ({error, isLoading, subscription}) => { 
      if (error) {
        return <Error error={error} />
      }

      if (isLoading) {
        return <p>loading...</p>
      }

      if (!subscription) {
        return <div>
          <p><strong>Subscribe to get paid features</strong></p>
          ..etc...
        </div>
      }

      ..etc...

    }}
  </FireflySubscription>
  }

  render() {
    return this.getPostSubscriptions(this.props.stripePlanId)
  }
}

Any clue most appreciated! The original code I'm adapting is from https://github.com/sampl/firefly if that helps.

Upvotes: 2

Views: 612

Answers (2)

Jagrati
Jagrati

Reputation: 12222

Going by the repository that you refer, its seems like you are rendering FireflySubscription from within Subscription component like

class Subscription extends React.Component {
    // other code here

    render() {
       return (
           <FireflySubscription>
               { ({error, isLoading, subscription}) => {
                   /*Some components here*/
               }}
           </FireflySubscription>
       )
    }
}

Considering the above, the simplest solution for you is to pass on the stripePlanId as a prop to FireflySubscription component and receive it inside the component along with children

Now that stripePlanId is calculated inside Subscription component, it can easily be passed to the children of FireflySubscription directly from parent without worrying about it being routed through FireflySubscription

So the solution would look like

class Subscription extends React.Component {
    // other code here

    render() {
       return (
           <FireflySubscription stripePlanId={this.props.stripePlanId}>
               { ({error, isLoading, subscription}) => {
                   // stripePlanId can be passed on to any children here using this.props.stripePlanId directly
                   /*Some components here*/
               }}
           </FireflySubscription>
       )
    }
}

Now in FireflySubscription, you will use it as

const FireflySubscription = ({children, stripePlanId}) => (
  <FirebaseAuth>
    { ({isLoading, error, auth}) => {
      if (error || isLoading || !auth) {
        //it pushes these arguments to the parent function
        return children({ 
          error,
          isLoading,
          subscription: null,
        })
      }

      if (stripePlanId) {
        return <FirestoreCollection
        path="subscriptions"
        filter={[['createdBy', '==', auth.uid], ['stripePlanId','==', stripePlanId]]}
      >
        { ({error, isLoading, data}) => {
          return children({
            error,
            isLoading,
            subscription: data.length > 0 ? data : null,
          })
        }}
      </FirestoreCollection>

      }

      return <FirestoreCollection
        path="subscriptions"
        filter={['createdBy', '==', auth.uid]}
      >
        { ({error, isLoading, data}) => {
          return children({
            error,
            isLoading,
            subscription: data.length > 0 ? data : null,
          })
        }}
      </FirestoreCollection>

    }}
  </FirebaseAuth>
)

Upvotes: 2

gdh
gdh

Reputation: 13682

Use Render Props

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.

A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.

ParentPost Component:

const ParentPost = () => {
    <Subscription auth={auth} stripeAmount={post.amount} stripePlanId={post.planid}>
        {(stripePlanId) => <FireflySubscription stripePlanId={stripePlanId}/>}
    </Subscription>
};

Subscription Component: In your render method, pass stripePlanId as prop to the children

class Subscription extends React.Component {
  // https://stripe.com/docs/checkout#integration-custom
  componentDidMount() {
    this.stripeCheckout = window.StripeCheckout.configure({
      // ...etc...
      email: this.props.auth.email
    });
  }

  newSubscription = () => {
    var stripePlanId = this.props.stripePlanId;
    this.stripeCheckout.open({
      amount: this.props.stripeAmount, // in cents
      description: this.props.stripePlanId,
      token: function(token) {
        createSubscription(token, stripePlanId);
      }
    });
  };
  
  render() {
      <div>
          ...
          {this.props.children(this.props.stripePlanId)}
          ...
      </div>
  }
}

FireflySubscription Component: In here, receive the stripePlanId from the parent like this:.

const FireflySubscription = ({children, stripePlanId}) => (
    <FirebaseAuth>
        {({isLoading, error, auth}) => {
            if (error || isLoading || !auth) {
                //it pushes these arguments to the parent function
                return children({
                    error,
                    isLoading,
                    subscription: null,
                })
            }

            
            //const stripePlanId = stripePlanIdFromParent; // dont need this as we are destructuring from props

            // when working this should return the subscription for only this planId
            if (stripePlanId) {
            ...

Upvotes: 2

Related Questions