2
votes

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:

2
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( <ModalRootContainer />, 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 <App />. 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
OK but how will this happen when I already have: ReactDOM.render( <Provider store={store}> <Router history={hashHistory}>{routing(store)}</Router> </Provider>, document.getElementById('root') );empyreal
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( <Provider store={store}><ModalRootContainer /></Provider>, document.getElementById('modals') );Dhruv Kumar Jha

2 Answers

1
votes

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
votes

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.