UIkit's modals in React: integration
Asked Answered
B

3

8

I'm working on this project where the frontend is in React with UIkit for the user interface. The integration between the parts looks poorly implemented. I'm going to explain why. There is a Modal component, something like

export class Modal extends Component {
  static getByName = name => UIkit.modal(`[data-modal-name='${name}']`)

  static show = name => {
    const modal = Modal.getByName(name)
    if (modal) modal.show()
  }

  static hide = name => {
    const modal = Modal.getByName(name)
    if (modal) modal.hide()
  }

  render() {
    // a modal
  }
}

this is used in this way

export const LoginFormModal = props => (
  <Modal name="login-form" className="login-form-modal" hideClose>
    <LoginForm />
  </Modal>
)

and show/hide is called programmatically where needed (even redux's actions)

Modal.hide("login-form")

this is in a Redux action, like this

export const login = credentials => {
  return dispatch => {
    dispatch(showLoader())

    API.authentication.login(
      credentials,
      response => {
        setCurrentUser(
          Object.assign({}, response.user, { user_id: response.user.id })
        )
        Modal.hide("login-form")
        dispatch(loginSucceded(response))
        dispatch(hideLoader())
        dispatch(push("/"))
        dispatch(fetchNotificationsCounter())
      },
      error => {
        dispatch(loginFailed(error))
        dispatch(hideLoader())
      }
    )
  }
}

This seems to work. Until you leave a component. When you come back to it, the second time the programmatically hide does not work anymore.

Anyone can lead me to how integrate the parts in a more react-appropriate way?

Bekki answered 27/3, 2018 at 11:37 Comment(4)
is redux state work with this? can you add code sample with your redux's actions.Bituminize
Added a redux action as an exampleBekki
It works the first time, my modal is closed programmatically. But, if I leave the component (unmount) e return to it, never works again.Bekki
you have to get state that indicate you have logged at LoginFormModalBituminize
R
4

Using the parts of uikit which manipulate the dom (show, hide) is obviously hard to connect with React (and probably you shouldn't), however:

You need to move the call of the functions show and hide inside the Component by passing the bool of the state of the modal (eg. modalopen) . A good hook is the componentWillReceiveProps which can be used to check the previus props

componentWillReceiveProps(nextProps) {
  if (nextProps.modalopen !== this.props.modalopen) {
    if (nextProps.modalopen) {
      getByName(...).show()
    } else {
      getByName(...).hide()
    }
  }
}

(this is inside the Modal class)

Rotherham answered 5/4, 2018 at 13:56 Comment(1)
You probably doing something similar for your loaded (assumption based on the showLoader and hideLoader in the dispatcher)Rotherham
I
2

The thing I don't like and that is definitely not a "React-way" is that the code is mutating state directly from an action creator (!). From React docs:

For example, instead of exposing open() and close() methods on a Dialog component, pass an isOpen prop to it.

So what if you had one modal that would be controlled by the redux state? Here is a possible implementation:

ModalWindow - will react to state changes and render depending what's in store:

import React from 'react';
import InfoContent from './InfoContent';
import YesOrNoContent from './YesOrNoContent';
import { MODAL_ACTION } from './modal/reducer';

class ModalWindow extends React.Component {
  renderModalTitle = () => {
    switch (this.props.modalAction) {
        case MODAL_ACTION.INFO:
          return 'Info';
        case MODAL_ACTION.YES_OR_NO:
          return 'Are you sure?';
        default:
          return '';
    }
  };

  renderModalContent = () => {
    switch (this.props.modalAction) {
      case MODAL_ACTION.INFO:
        return <InfoContent />;
      case MODAL_ACTION.YES_OR_NO:
        return <YesOrNoContent />;
      default:
        return null;
    }
  };

  render() {
    return (
        this.props.isModalVisible ?
        <div>
           <p>{this.renderTitle()}</p> 
           <div>
              {this.renderModalContent()}
           </div>
        </div>
        :
        null
    );
  }
}

export default connect((state) => ({
    modalAction: state.modal.modalAction,
    isModalVisible: state.modal.isModalVisible,
}))(ModalWindow);

modal reducer it will expose API to show/hide modal window in the application:

export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';

const INITIAL_STATE = {
  isModalVisible: false,
  modalAction: '',
};

export default function reducer(state = INITIAL_STATE, action) {
  switch (action.type) {
    case SHOW_MODAL:
      return { ...state, isModalVisible: true, modalAction: action.modalAction };
    case HIDE_MODAL:
      return { ...state, isModalVisible: false };
    default:
      return state;
  }
}

export const MODAL_ACTION = {
  YES_OR_NO: 'YES_OR_NO',
  INFO: 'INFO',
};

const showModal = (modalAction) => ({ type: SHOW_MODAL, modalAction });
export const hideModal = () => ({ type: HIDE_MODAL });
export const showInformation = () => showModal(MODAL_ACTION.INFO);
export const askForConfirmation = () => showModal(MODAL_ACTION.YES_OR_NO);

So basically you expose simple API in form of redux action-creators to control the state of your ModalWindow. Which you can later use like:

dispatch(showInformation())
...
dispatch(hideModal())

Of course, there could be more to it like optional configuration that would be passed to action creators or queue for modals.

Illusionism answered 2/4, 2018 at 17:0 Comment(0)
D
0

I use a combination of a hook and a component for this.

Hook:

import { useState } from "react";
import UIkit from "uikit";

export default function useModal() {
  const [isOpen, setIsOpen] = useState(false);
  const [ref, setRef] = useState(null);

  const open = (e) => {
    UIkit.modal(ref).show();
    setIsOpen(true);
  };

  const close = (e) => {
    UIkit.modal(ref).hide();
    UIkit.modal(ref).$destroy(true);
    setIsOpen(false);
  };

  return [setRef, isOpen, open, close];
}

Component:

import React, { forwardRef } from "react";

const Modal = forwardRef(({ children, isOpen, full, close }, ref) => (
  <div
    ref={ref}
    data-uk-modal="container: #root; stack: true; esc-close: false; bg-close: false"
    className={`uk-flex-top ${full ? "uk-modal-container" : ""}`}
  >
    <div className="uk-modal-dialog uk-margin-auto-vertical">
      <button
        type="button"
        className="uk-modal-close-default"
        data-uk-icon="close"
        onClick={close}
      />
      {isOpen && children()}
    </div>
  </div>
));

export default Modal;

Consumption:

function Demo() {
  const [ref, isOpen, open, close] = useModal();
  return (
    <div>
      <button
        type="button"
        className="uk-button uk-button-default"
        onClick={open}
      >
        upgrade
      </button>
      <Modal isOpen={isOpen} close={close} ref={ref} full>
        {() => (
          <div>
            <div className="uk-modal-header">
              <h2 className="uk-modal-title">title</h2>
            </div>
            <div className="uk-modal-body">
                body
            </div>
          </div>
        )}
      </Modal>
    </div>
  );
}

Read more: https://reactjs.org/docs/integrating-with-other-libraries.html

Diatropism answered 31/7, 2022 at 15:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.