jquense / react-bootstrap-modal

React port of jschr's better bootstrap modals
Other
89 stars 51 forks source link

extending <ModalExample> for real-world use cases #36

Closed godmar closed 7 years ago

godmar commented 7 years ago

I'm trying to develop a component '' based on this wrapper for Bootstrap's modal by extending the ModalExample given.

My use case is a table where each row i has a 'delete' button which, when clicked on, displays the ConfirmDialog modal and asks the user to confirm that they wish to delete row number i.

My questions are (a) how many components should I render, and (b) how to show/hide them.

In a non-React world, I would probably create a single ConfirmDialog instance, add an 'open/close' method, and call open() in the onClick handler for each parent row, passing along the index of the row and maybe some other information. Maybe like this:

   let confirm = <ConfirmDialog> ... </ConfirmDialog>
   rows.map((r) => <Row onClick={confirm.open({ msg: `Do you really want to delete ${r.id}`, row: r.id })} > ...

(In fact, I can make this work in React.JS using the ref property to refer to the rendered 'ConfirmDialog', and access its open method in this way. But since the component will already have rendered, I can't pass along any information that depends on the row that caused it to open.)

In React - I can solve the problem of passing along per-row information by rendering one ConfirmDialog instance per row - which answers (a), and address problem (b) by adding state to the parent component that control's ConfirmDialog's show property to make each row's ConfirmDialog visible or not. Kind of like so:

   rows.map((r) => <div>
          <ConfirmDialog show={this.state.rowsOpen[i]}>Do you really want to delete {r.id}....
          <Row onClick={this.setState((clone state + set state.rowsOpen[i] to true)} > ///

when using redux, I would possibly have to dispatch an action just to set 'rowsOpen[i]' to true.

This seems wasteful and awkward though, although it is difficult to ascertain its true cost, given what React.JS does under the hood. But it may well be the idiomatic way.

I would appreciate an example or advice on how to use this provided component in a real-world context.

jquense commented 7 years ago

your approach above is fine, i'm not sure what the cost would be for redux but presumably its designed to handle lots of actions.

More generally tho for "dialog" style modals, i usually create an imperative wrapper around the modal components, since dialog usage is almost always better served as an imperative action vs a declarative one like this:

function dialog(modelElement) {
  return new Promise((resolve) => { 
    let mountNode = document.createElement('div');
    let show = true;

    render();

    function onExited() {
      if (!mountNode)  return;
      ReactDOM.unmountComponentAtNode(mountNode);
      mountNode = null;
    }

    function render() {
      ReactDOM.render(
        React.cloneElement(modelElement, {
          show,
          onExited,
          onHide(action) {
            show = false;
            resolve(action);
            render();
          },
        }),
        mountNode
      );
    }
  });
}

then you can do something like

 dialog(<ConfirmDialog>...</>)
   .then(answer => { 
      if (answer)  delete() 
   })
godmar commented 7 years ago

Thank you. Your code is probably just a sketch - would it actually work this way?
Specifically, does calling ReactDOM.render(...., unattachedDOMnode) have any effect, or would you actually have to render the Modal into an element that is attached to the DOM?

jquense commented 7 years ago

the above should work. due to the way the modal is implemented the dome node you render it into doesn't need to be attached

godmar commented 7 years ago

PS: It did work, thank you so much!