2

We are implementing an application in React-Redux with stateless functional components and containers for the ones that need to handle state. The idea is to have all information in the Redux store.

In the application we need to show in several places different modal windows. I have looked into the articles here in stack overflow, but I cannot get this working. I have followed the process described by Dan Abramov in this post.

My main question is where I should render my ModalRoot component inside my application. The process is the following:

  1. I have a button that dispatches an action displayModal in order to display a modal...

    clickHandler: modalProps => (
    dispatch(displayModal('MODAL_1', modalProps))
    
    export function displayModal(modalType, modalProps) {
      return {
        type: DISPLAY_MODAL,
        payload: {
          modalType,
          modalProps
        }
      };
    }
    
  2. Then the action gets handled by a reducer that sets the modalType property in the redux-state...

    export default function (modal = Map(), action) {
      if (!action) {
        return modal;
      }
      switch (action.type) {
        case DISPLAY_MODAL:
          return modal
            .set('modalType', action.payload.modalType)
            .set('modalProps', action.payload.modalProps);
        default:
          return modal;
      }
    }
    
  3. Then I have the ModalRootContainer that passes the state to my ModalRoot component...

    const mapStateToProps = state => (
      {
        modalType: modalState(state).get('modalType'),
        modalProps: modalState(state).get('modalProps')
      }
    );
    
    const ModalRootContainer =
      connect(mapStateToProps)(ModalRoot);
    
    export default ModalRootContainer;
    
  4. ModalRootComponent is as follows:

    const ModalRoot = (modalType, modalProps) => {
      if (!modalType) {
        return &ltspan />;
      }
    
      switch (modalType) {
        case 'MODAL_1':
          return &ltMyComponent {...modalProps}/>;
        default:
          return &ltspan />;
      }
    };
    
    ModalRoot.propTypes = {
      modalType: PropTypes.string,
      modalProps: PropTypes.object
    };
    
    export default ModalRoot;
    

My understanding is that this is correct but it does not work. Where should ModalRoot be placed? Inside the Provider tag? What am I missing?

Edit 1: I am trying to use what Random User is saying in his answer below with no effect. So I have in my index.html:

<body>
<div id="modals"></div>
<div id="root"></div>
</body>

In my main.js:

ReactDOM.render(
      <Provider store={store}>
        <Router history={hashHistory}>{routing(store)}</Router>
      </Provider>,
      document.getElementById('root')
    );
    ReactDOM.render(
      <Provider store={store}>
        <ModalRootContainer />
      </Provider>,
      document.getElementById('modals')
    );

And with all the above setup still no modal is being shown. I debug and I see that the action is triggered and the state is updated from the reducer. Is there a way to debug after this point to see if something is wrong with my container and why is the modal not shown?

Edit 2: I have found what was wrong. I was passing wrongly the state to my container. The code seems to work, however now instead of having a modal window show, I am having my component (supposed to be inside the modal) appear above my remaining page. I am just rendering for the moment a simple text "Close modal" and a button to close it. It is shown and hidden correctly but it does not work as a modal, it just shows and hides. Any help on what I am missing?

Hidden:

Shown:

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
empyreal
  • 177
  • 1
  • 6
  • 1
    For modals, I think it will be best to create a new node/div in your html document and place that component in there `ReactDOM.render( , document.getElementById('modals') );` While the entire app can render in a separate document node. This will allow you to style the model however you want and not affect the structure/ui of your ``. you can also use this for showing loading messages, alerts, this will act as an additional layer for different type of ui. – Dhruv Kumar Jha Feb 16 '17 at 11:30
  • OK but how will this happen when I already have: `ReactDOM.render( {routing(store)} , document.getElementById('root') );` – empyreal Feb 16 '17 at 11:51
  • Just add a new `ReactDOM.render` below that, You can call `ReactDOM.render` as many times as you want., Unless `Modals` are integral part of you entire app, I would recommend you to use some existing libraries which provides Modals. You can place your additional code like `ReactDOM.render( , document.getElementById('modals') );` – Dhruv Kumar Jha Feb 16 '17 at 11:56

2 Answers2

1

You should render ModalRootContainer inside your current Provider. You probably have some app.js file with <Provider> ... </Provider> inside. This is the place where you should render your container.

It's because redux shares state through context (https://facebook.github.io/react/docs/context.html). Provider is a component that creates that context and thanks to connect you have its data in props. So to have access to state you must render all containers as children/grandchildren/... of Provider component.

1

You have declared a ModalRootComponent but to actually use it you have to render it somewhere in your application. What I mean by render is to put it inside reacts ReactDOM.render(...) function.

When you are using redux your rendering function will usually look like this with Provider tag on top of everything:

ReactDOM.render(
  <Provider store={store}>

    ...

  </Provider>,
  document.getElementById('root')
);

What you can do to render the ModalRootContainer component is just to put it inside the Provider tag like this:

ReactDOM.render(
  <Provider store={store}>

    <ModalRootContainer />

  </Provider>,
  document.getElementById('root')
);

But in a more complex scenario you would go with rendering the router here and then inside each route you would actually do the content:

ReactDOM.render(
  <Provider store={store}>

    <Router history={history}>
      {routes}
    </Router>

  </Provider>,
  document.getElementById('root')
);

Also make sure that you have set up redux store right and it knows about your reducer. You can see working todo example with react and redux here.

patotoma
  • 1,069
  • 2
  • 11
  • 15
  • Thank you. I am having the second case that you mention with the routing. In this case where should my ModalContainer go? I am getting error: "React.Children.only expected to receive a single React element child." while doing the following: ReactDOM.render( {routing(store)} , document.getElementById('root') ); – empyreal Feb 16 '17 at 11:46
  • 1
    @empyreal thats because **** expects only one children node inside it so you have to wrap the content of it either inside
    ...
    or some other custom component e.g.
    – patotoma Feb 16 '17 at 13:26
  • Thank you. Won't `
    ` cause my page to show first the contents of the modal and then the Remaining application in each page? It still is not working though. I am missing something.
    – empyreal Feb 16 '17 at 13:40
  • @empyreal there is also a tool for new react users called [create-react-app](https://github.com/facebookincubator/create-react-app) that scafolds basic react application for you in no time and you can follow some tutorial to add redux into it like [this one](http://www.penta-code.com/how-to-add-redux-to-create-react-app/) – patotoma Feb 16 '17 at 14:10