[My use of] react-bootstrap popover "react way"?

I'm working on a React app, my first, and I'm struggling with how to implement custom interactive popovers. I have a business requirement to have a popover with yes / no buttons. This popover is triggered by a button ("pobtn") that lives on a row in a table in a view:

+- View ---------------------+          +- View ---------------------+
| +- Table ----------------+ |   click  | +- Table ----------------+ |
| | +- Row --------------+ | |    -->   | | +- Row --------------+ | |
| | | ...                | | |   pobtn  | | | ...                | | |
| | +--------------------+ | |          | | +--------------------+ | |
| | +- Row --------------+ | |   ,--------. +- Row --------------+ | |
| | | ...        [pobtn] | | |   | Yes/No |>| ...        [pobtn] | | |
| | +--------------------+ | |   `--------' +--------------------+ | |
| | +- Row --------------+ | |          | | +- Row --------------+ | |
| | | ...                | | |          | | | ...                | | |
| | +--------------------+ | |          | | +--------------------+ | |
| | ...                    | |          | | ...                    | |
| +------------------------+ |          | +------------------------+ |
+----------------------------+          +----------------------------+

      

View, Table and Row are all components. The tabular data is fetched in the view and put into the table via props and that trickles down into the row, which I understand is the "right way" to do this and makes sense. There are not many local staff.

Popover problem. "Correctly" to do this seems to be to include a Popover component along each button and push props down for hidden / visible I guess. It doesn't seem very big, but closer to the correct way to do it in my mind.

However, the pop-pop-react-bootstrap (which I used because the timing) doesn't seem to work this way:

var content = <MyRowPopoverContent/>;
<OverlayTrigger trigger='click' placement='left' overlay={<Popover>{content}</Popover>
    <button className="btn btn-primary small"><i className="fa fa-trash-o"></i> Delete</button>
</OverlayTrigger>

      

When you press the trigger button, it creates a DIV attached to the body and positions itself next to the trigger element, similar to how it works fine (not responsive). It looks like I could connect the DIV to a different container, but this is not well documented. In any case, the content of the overlay (i.e., MyRowPopoverContent) looks something like this:

this.handleNoClick: function(){
  // Re-click the button to close; gross.
  $('[data-row-id="'+this.props.row.key+'"]').find('.fa-trash-o').parents('button').first().click();
},
this.handleYesClick: function(){
  // Do stuff...update store which would re-render the view.
  // Re-click the button to close; gross.
  $('[data-row-id="'+this.props.row.key+'"]').find('.fa-trash-o').parents('button').first().click();
},
this.render: function(){
  <div>
    <div>Are you sure?</div>
    <input placeholder="Reason"/>
    <button onClick={this.handleYesClick}>Yes</button> <button onClick={this.handleNoClick}>No</button>
  </div>
}

      

I started implementing the function handleYesClick

trying to do error handling, asynchronous events, etc., and it all seems really sketchy, or at least fragile. I guess the question is: I need an interactive popover, how do I do this? It looks like the popup will be part of the local state of the button or line, or maybe this is creeping up, as most things seem to do.

Since action-bootstrap already provides one, it would be nice to use that, but I'm not sure how to "hook it up" to everything else.

Update: If it was a simple static popover that would be cake; showing / hiding it via props and clicking is pobtn

pretty trivial. The part I'm running into is the interactive content inside the popover - do something (like an event to refresh the store), show a spinner, determine if something is working, show an error message, or disappear. Even clicking the No button and the popover disappearing is not clear (I currently find it using jQuery and button .click()

). Doing this through props from the parent seems intimidating ...

+3


source to share


1 answer


Let's say you are encapsulating your popover in a component PopoverMenu

. Here's a template suitable for react where the parent handles child events:

var Table = React.createClass({
  handlePopoverClick(selection, meta) {
    // selection - the selection ('Yes' or 'No' in your case)
    // meta - anything you want to know about the caller
  },
  render: function() {
    var open = /* boolean logic to determine if popover is open or closed* /;
    // key idea is to pass the child a handler from the parent
    <PopoverMenu handler={this.handlePopoverClick} open={open} ... />
    ...
  }
});

var PopoverMenu = React.createClass({
  handleClick: function(selection, meta) {
    this.props.handler(selection, meta);
  },
  render: function() {
    ...
    //somewhere you render the 'Yes' Button of this popover
    <Button onClick={this.handleClick.bind(this, 'Yes', {row: 2})} />
  }
});

      

The component PopoverMenu

has no status; he knows enough to do. Its handler receives the click event and then adjusts the props PopoverMenu

as needed. The magic is in .bind

, allowing you to enter caller-specific information for the handler. (You can imagine what .bind

occurs in the call map

, where Yes

and{row :2}

are dynamic variables.) Note that you can handle all popover variants with a single handler in the parent and that you can easily track, as part of the parent state (like Row), the pending / success / failure state of any calls. It may seem scary, but the logic has to live somewhere :) and it's better if it's not a child somewhere, since the React method is stateless children. The state naturally becomes a props, as information flows from parent to child.



However, if your parent does too much bookkeeping and you don't get clean, autonomous behavior from the child components, you can push logic to the children, usually through reusing the children. It is reasonable and possible for the child to manage his / her open / closed state and expectation / error / success. If you go this route, one pattern you can use is to put the child in a wait state until you get the appropriate value from the parent via props. For example, if the user makes a menu selection "X" that requires a callback, the child executes setState{ selection: 'X'}

and waits for the parent to confirm via props (which reflects the state of the store):

if (this.state.selection !== this.props.selection) {
  //render component as waiting
} else {
  //render as normal
}

      

As far as the open / close status of the popover is concerned, it would be easier if a type component is used instead that ButtonDropdown

manages its own open / close state. (You may need to set an empty handler onSelect

in the dropdown so that it closes automatically on selection.)

+1


source







All Articles