Search code examples
javascriptreactjscomponentsreact-props

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.


Solution

  • 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>
    )