How can I set up a react modal to open from anywhere in the app?

2.5k Views Asked by At

I'm relatively new to React, but I'm trying to set up a modal that will open from anywhere. Here's how I'm trying to open a modal:

class SearchResults extends React.Component {
  static propTypes = {
    status: React.PropTypes.string.isRequired,
    results: React.PropTypes.array.isRequired,
    onResultClick: React.PropTypes.func.isRequired
  }

  constructor(props) {
    super(props)
  }

  onUploadButtonClick() {
    console.log('here');
  }

  render () {
    const uploadButtonFn = this.onUploadButtonClick
    let childElements = [];

    childElements.unshift((
      <div className="upload-button-wrapper" key="upload-button">
        <button type="button" className="upload-button" onClick={uploadButtonFn}><span>+</span> Upload Your Vidy</button>
      </div>
    ))

    return (
      <div className='search-results'>
        <div className='inner-content'>
          {(() => {
            if (this.props.status === 'fetching') {
              return <h1>searching</h1>
            }

            if (this.props.status === 'ready') {
              return (
                <Masonry
                    className={'my-gallery-class'} // default ''
                    elementType={'div'} // default 'div'
                    disableImagesLoaded={false} // default false
                    updateOnEachImageLoad={false} // default false and works only if disableImagesLoaded is false
                >
                  {childElements}
                </Masonry>
              )
            }
          })()}
        </div>
        <GetMoreResult />
      </div>
    )
  }
}

export default SearchResults

That's how Im trying to open a modal. I'm almost positive there are far better ways. I'm using redux, I think. I've inherited the project so I may not have it all right.

Any help would be greatly appreciated. I think I need to include the modal in my main layout, but then how do I trigger it?

2

There are 2 best solutions below

0
On BEST ANSWER

Not sure how your code works really, where is the connect()? Anywhere here's what I think you want using redux. This is just an example so you can see how we can show a modal from any other component even if it's not related:

//action
const toggleModal = (isVisible) => ({
    type: "TOGGLE_MODAL",
    isVisible: isVisible
});

//reducer
export default (state={}, action) => {
    switch (action.type) {
        case "TOGGLE_MODAL":
            return {...state, isVisible: action.isVisible}
        default:
            return state;
    }
}

//connect() allows us to dispatch an action
class RandomClassElseWhereInApp extends connect()(React.Component {
    constructor(props) {
        super(props)
    }
    OpenModal = () => this.props.dispatch(toggleModal(true))
    render () {
        return (
        );
    }
})

//Gives us access to the store
const mapStateToProps = (state) => ({
    state.Modal.isVisible
});

//This is a smart component
class Modal extends connect(mapStateToProps)(React.Component {
  constructor(props) {
    super(props)
  }
  componentWillReceiveProps(nextProps) {
    //Empty string for display sets it to original value
    const display = "" ? this.props.isVisible : "none";

    this.setState({myModalStyle: {display: display}});
  }
  render () {
    return (
        <Popup style={this.state.myModalStyle}/>
    );
  }
})

//This is a dumb component
const Popup = ({style}) => (
    <div class="popup" style={style} />
);
0
On

Our solution to this goes something like this: If you're using redux and react-redux.

First off, make some action creators and some redux state to control which modal is showing.

// modal-action-reducer.js

export const ActionTypes = {
  CLOSE_MODAL: 'CLOSE_MODAL',
  OPEN_MODAL: 'OPEN_MODAL'
};

export const ActionCreators = {
  OpenModal = id => ({ type: ActionTypes.OPEN_MODAL, payload: { id } }),
  CloseModal: () => ({ type: ActionTypes.CLOSE_MODAL })
};

// this part of the state will be on something like `state.visibleModal`
export const reducer = (state = null, action) => {
  switch (action.type) {
    case ActionTypes.OPEN_MODAL:
      return action.payload.id;

    case ActionTypes.CLOSE_MODAL:
      return null;

    default:
      return state;
  }
};

Then make a higher order component to decorate your actual modal UI.

// ModalDecorator.jsx

import React from 'react';
import {connect} from 'react-redux';
import {ActionCreators} from './modal-action-reducer.js';

export default function ModalDecorator (modalId) {
  return function (WrappedModalComponent) {

    class WrapperComponent extends React.Component {
      render () {
        const {isVisible, ...otherProps} = this.props;
        return isVisible
          ? <WrappedModalComponent {...otherProps} />
          : <div style={{ display: 'none' }} >;

      } 
    }

    // get whether this modal is visible by checking the state from the reducer against the modal id
    const mapStateToProps = state => ({
      isVisible: state.visibleModal === modalId
    });

    // for convenience we can add a closeModal prop to all modals
    const mapDispatchToProps = dispatch => ({
      closeModal: () => dispatch(ActionCreators.CloseModal());
    });

    return connect(mapStateToProps, mapDispatchToProps)(WrapperComponent);
  }
}

Then in Your actual modal:

// MyModal.jsx

import React from 'react';
import ModalDecorator from './ModalDecorator.jsx';

class MyModal extends React.Component {
  render () { /* Do your thing here */ }
}

export default ModalDecorator('my-modal-id')(MyModal);

One caveat here is that all modals have to be rendered somewhere, so make a basic container for them:

// ModalDeclarations.jsx
import MyModal from './MyModal.jsx';
import SomeOtherModal from './SomeOtherModaal.jsx';

const ModalDeclarations = () => (
  <div>
    <MyModal/>
    <SomeOtherModal/>
  </div>
);

export default ModalDeclarations;

And in App.jsx

import React from 'react'
import ModalDeclarations from './ModalDeclarations.jsx';

export default class App extends React.Component {
  render () {
    return (
      <div>
        {/* Other bits of your app here */}
        <ModalDeclarations />
      </div>
    );
  }
}

Finally, we can use the OpenModal action creator anywhere to open any modal. This is a button that will open the modal using modalId from it's props.

// ModalButton.jsx

import React from 'react';
import {connect} from 'react-redux';
import {ActionCreators} from './modal-action-reducer.js';

const mapDispatchToProps = (dispatch, ownProps) => ({
  openModal: () => dispatch(ActionCreators.OpenModal(ownProps.modalId))
});

class ModalButton extends React.Component {
  render () {
    return (
      <div onClick={this.props.openModal} >
        {this.props.children}
      </div>
    )
  }
}

export default connect(null, mapDispatchToProps)(ModalButton);

ModalButton.propTypes = {
  modalId: React.PropTypes.string.isRequired
};