6

I'm using React and Material UI for a project, and I'm implementing payment methods. I will basically support different type of payment methods, and depending which one user chooses I want to display a form, and once the user fills the form and submits it, I want to return to my previous view. I have a component that looks like this:

import React, { Component } from 'react';
import { List, ListItem } from 'material-ui';
import { Button } from '../../components';
import StripeForm from './stripe/form';
import PropTypes from 'prop-types';

class MyComp extends Component {
    constructor(props) {
        super(props);

        this.state = {
             paymentProviders: [],
             indexOfClickedItem: -1,
        };

        this.onListItemClicked = this.onListItemClicked.bind(this);
        this.onPayment = this.onPayment.bind(this);
    }

    onListItemClicked(paymentProvider, index) {
        let paymentProviders = {
            providerName: paymentProvider.ProviderName,
            publicKey: paymentProvider.PublicKey,
        };
        this.setState({
            paymentProviders: paymentProviders,
            indexOfClickedItem: index,
        });
    }

    onPayment() {
        switch (this.state.paymentProviders.providerName) {
             case "stripe":
                 // render the form for stripe payment
                 return ( <StripeForm /> );

             case "paypal":
                 // render the form for paypal payment

             default:
                 return null;
         }
    }

    render() {
        return (
            <div>
                <div>
                    <List>
                        {this.props.paymentProviders.map((pp, index) => {
                            return (
                                <ListItem key={pp.ProviderName}
                                        primaryText={pp.ProviderName}
                                        onClick={() => this.onListItemClicked(pp, index)}
                                />
                            )
                        })}
                    </List>
                </div>

                <div>
                    <Button onClick={this.onPayment}
                            label="Pay" />
                </div>
            </div>
        );
    }
}

MyComp.propTypes = {
    paymentProviders: PropTypes.array.isRequired,
};

export default MyComp;

So, basically, depending on which ListItem is clicked I update my indexOfClickedItem in the state, and then also update some details about the payment provider. But, what I mainly want to achieve is that, when my button is clicked, depending on which payment provider is chosen (meaning which list is previously clicked/selected), I want to render another component. Any ideas how to achieve it?

5
  • You can use React Router, especially Switch Commented Oct 16, 2017 at 16:54
  • So you have options, you can also use inline conditional render to hide and show your components based on the component's state. Commented Oct 16, 2017 at 16:57
  • @fungusanthrax code sample is highly welcomed. Commented Oct 16, 2017 at 17:49
  • render() => { var show = this.state.modalIsOpen; return ( {show === true && <Modal /> } {show === false && <Modal2 /> } ) } See here for reference: reactjs.org/docs/conditional-rendering.html Commented Oct 16, 2017 at 17:51
  • @fungusanthrax Yes, I know the idea of conditional rendering, but I don't exactly see how to apply that to my scenario, as I need the new component to be rendered only on button click. Commented Oct 16, 2017 at 19:51

2 Answers 2

8

You can use conditional rendering.

First initialize a variable inside constructor to store the form, and add a state viewForm to manage whether to view the form or not:

constructor(props) {
    super(props);

    this.state = {
         paymentProviders: [],
         indexOfClickedItem: -1,
         viewForm: false
    };

    this.onListItemClicked = this.onListItemClicked.bind(this);
    this.onPayment = this.onPayment.bind(this);
    this.paymentForm = null;  //this
}

On button click, conditionally assign a value to it when a payment mode is selected, and change the state to view the form:

    switch (this.state.paymentProviders.providerName) {
         case "stripe":
             this.paymentForm = <StripeForm/>
             break;
         case "paypal":
             this.paymentForm = <PaypalForm/>
             break;
         default:
              this.paymentForm = null;
     }
     this.setState({ viewForm: true });

.. and then render it conditionally:

<div className='form-container'>
  {(this.state.viewForm) ?
  this.paymentForm : ''}
</div>
Sign up to request clarification or add additional context in comments.

3 Comments

You don't need that variable on this. You can just store the dynamic component in a local variable. let component; if (this.state.paymentProviders.providerName === 'stripe') { component = <StripeForm /> } else if (...) { ... } ............ <div>{component}</div>
Yes, but how do you make sure that the new view is rendered only on button click and not on ListItem click?
@typos how about keeping track whether you clicked that using a component state (boolean) property?
1

just add a conditional to your render method which examines state and renders the new component if the conditions are right:

render() {
    // render payment provider component for selected provider
    const {paymentProviders, indexOfClickedItem, paymentClicked} = this.state;
    const selectedPP = paymentProviders && paymentProviders[indexOfClickedItem];
    if (paymentClicked) {
        // whatever mechanism you want to use to decide
        // what to render for this payment provider...
        const ComponentToRender = selectedPP.Component;
        return (
          <ComponentToRender
            // unselects the item when the user finishes the payment
            onComplete={() => this.setState({indexOfClickedItem: -1})}
            prop1={"foo"}
          />
        );
    }

    // if nothing selected, render list
    return (
        <div>
            <div>
                <List>
                    {this.props.paymentProviders.map((pp, index) => {
                        return (
                            <ListItem key={pp.ProviderName}
                                    primaryText={pp.ProviderName}
                                    onClick={() => this.onListItemClicked(pp, index)}
                            />
                        )
                    })}
                </List>
            </div>

            <div>
                <Button onClick={this.onPayment}
                        label="Pay" />
            </div>
        </div>
    );
}

Your onPayment function should issue a this.setState({paymentClicked: true}) to trigger the new render path.

4 Comments

This is a bit different than what I want. In your code you basically render a new component whenever the user clicks on one of the ListItems. But what I want to do is that I want to render a new component whenever the user clicks the Pay button. I have deleted the styles for brevity, but what happens in my code is that when the user clicks on one of the ListItems, the clicked ListItem changes style, so user is aware of which one he/she has clicked, and additionally I update the indexOfClickedItem. Then, when the user presses the Pay button, that's when I want to render the new component.
It's the same concept.
@nbkhope Then, I don't see how it is the same. Can you please update the code to reflect the changes?
I added a couple of lines of code to show you how to trigger it from onPayment. It is really the same idea: when the user triggers some event, you call setState to set some data, then your render method checks for that data and renders something different.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.