简体   繁体   中英

Modal component does not render on a custom button component

I am trying to render a custom and dynamic modal on button clicks. For example, when a "Game" button is clicked, I would like a modal to render with specfics about the game and when a "Bank" button is clicked, I would like the modal to populate with specfics about a bank.

First, when I add an onClick function to a custom button component, the modal does not render. However, when I put the onClick function on a regular button, the modal does render. How can I simply add an onClick function on any component to render a dynamic modal?

Second, I would like to populate each modal with differnet data. For example, a "Game" button would populate the modal with a title of "Game" and so on. I'm using props to do this, but is that the best solution?

Here is the code I have so far, but it is broken when I add the onClick function to components.

// Navbar.js
import { ModalContext } from '../contexts/ModalContext'

function Navbar() {
  const [showModal, updateShowModal] = React.useState(false)
  const toggleModal = () => updateShowModal((state) => !state)

return(
<ModalContext.Provider value={{ showModal, toggleModal }}>
  <Modal
  title="Title"
  canShow={showModal}
  updateModalState={toggleModal}
  />
  </ModalContext.Provider>
 )

  // does not render a modal
  <Button
  onClick={toggleModal}
  type="navItem"
  label="Game"
  icon="windows"
  />

    // render a modal
    <button onClick={toggleModal}>Show Modal</button>
  )
}
import { ModalContext } from '../contexts/ModalContext'
// Modal.js
const Modal = ({ title }) => {
  return (
    <ModalContext.Consumer>
      {(context) => {
        if (context.showModal) {
          return (
            <div style={modalStyles}>
              <h1>{title}</h1>
              <button onClick={context.toggleModal}>X</button>
            </div>
          )
        }

        return null
      }}
    </ModalContext.Consumer>
  )
}
// modalContext.js
export const ModalContext = React.createContext()
// Button.js
function Button({ label, type = 'default', icon }) {
  return (
    <ButtonStyle buttonType={type}>
      {setIcon(icon)}
      {label}
    </ButtonStyle>
  )
}

First problem:

I think the onClick prop of the <Button> component is not pointing to the onClick of the actual HTML button inside the component. Could you please check that? And if you think It's been set up in the right way, then can you share the code of the component?

Second Problem

Yes, there's another way to do that. And I think it's React Composition . You can build the modal as the following:

<Modal
  showModal={showModal}
  updateModalState={toggleModal}
>
  <div className="modal__header">{title}</div>
  <div className="modal__body">{body}</div>
  <div className="modal__footer">{footer}</div>
</Modal>

I think this pattern will give you more control over that component.

Issue

You are not passing the onClick prop through to the styled button component.

Solution

Given style-component button:

const ButtonStyle = styled.button``;

The custom Button component needs to pass all button props on to the ButtonStyle component.

// Button.js
function Button({ label, type='default', icon, onClick }) {
  return (
    <ButtonStyle buttonType={type} onClick={onClick}>
      {setIcon(icon)}
      {label}
    </ButtonStyle>
  )
}

If there are other button props then you can use the Spread syntax to collect them into a single object that can then be spread into the ButtonStyle component.

// Button.js
function Button({ label, type = 'default', icon, ...props }) {
  return (
    <ButtonStyle buttonType={type} {...props}>
      {setIcon(icon)}
      {label}
    </ButtonStyle>
  )
}

Second Question

For the second issue I suggest encapsulating the open/close/title state entirely in the modal context provider, along with the Modal component.

Here's an example implementation:

const ModalContext = React.createContext({
  openModal: () => {},
});

const Modal = ({ title, onClose}) => (
  <>
    <h1>{title}</h1>
    <button onClick={onClose}>X</button>
  </>
)

const ModalProvider = ({ children }) => {
  const [showModal, setShowModal] = React.useState(false);
  const [title, setTitle] = React.useState('');

  const openModal = (title) => {
    setShowModal(true);
    setTitle(title);
  }

  const closeModal = () => setShowModal(false);

  return (
    <ModalContext.Provider value={{ openModal }}>
      {children}
      {showModal && <Modal title={title} onClose={closeModal} />}
    </ModalContext.Provider>
  )
}

Example consumer to set/open a modal:

const OpenModalButton = ({ children }) => {
  const { openModal } = useContext(ModalContext);

  return <button onClick={() => openModal(children)}>{children}</button>
}

Example usage:

function App() {
  return (
    <ModalProvider>
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>

        <OpenModalButton>Modal A</OpenModalButton>
        <OpenModalButton>Modal B</OpenModalButton>
      </div>
    </ModalProvider>
  );
}

Demo

编辑 modal-component-does-not-render-on-a-custom-button-component

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM