mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.96k stars 32.27k forks source link

Full screen dialog and back button on mobile #12759

Closed ghost closed 6 years ago

ghost commented 6 years ago

Having a full screen dialog really is a huge improvement for mobile. But it really really needs to support the Back button. It's so counter intuitive for users when they hit the back button on their mobile and navigate them the actual page they were on.

Is it hard to add this because of the different routers used ?

oliviertassinari commented 6 years ago

It's outside of the scope of the library. You need to wire the open state with the URL.

ghost commented 6 years ago

@oliviertassinari ok thanks.

Will search for an example of how to do that online.

ValentinH commented 6 years ago

I almost thought about this issue at the same time as you @gedw99! ^^ If you find something nice, could you please share it here ? :)

jlramosr commented 5 years ago

Any solution for this?

JulesAU commented 5 years ago

I'm a bit confused because the mUI docs for the Dialog example state that:

Touching outside of the dialog, or pressing Back, cancels the action and closes the dialog

But this does not appear to be the case?

ValentinH commented 5 years ago

@JulesAU Indeed I think this sentence is not correct.

I've made a demo that binds the open state with the location hash, like this, clicking the back button works as expected: https://codesandbox.io/s/material-demo-7zf07 (the back button does not seem to work as expected in Codesandbox preview, but if you open the result on a dedicated page, it works as expected: https://7zf07.codesandbox.io)

@oliviertassinari do you think this could be added to the docs?

oliviertassinari commented 5 years ago

@ValentinH Thanks for sharing your demo. I believe this issue is enough as documentation.

HazyFish commented 2 years ago

I made a React hook useUrlHashState() based on @ValentinH 's answer.

import { SetStateAction, useEffect, useState } from 'react';

/**
 * use a boolean state that binds to a url hash
 * @param hash a string starts with '#'
 * @returns current state, and a function to update it
 */
export function useUrlHashState(
  hash: string
): [state: boolean, setState: React.Dispatch<SetStateAction<boolean>>] {
  const [state, setState] = useState<boolean>(window.location.hash === hash);

  useEffect(() => {
    const onHashChange = () => setState(window.location.hash === hash);
    window.addEventListener('hashchange', onHashChange);
    return () => window.removeEventListener('hashchange', onHashChange);
  }, [hash]);

  return [
    state,
    setStateAction => {
      if (
        (typeof setStateAction === 'boolean' && setStateAction) ||
        (typeof setStateAction === 'function' && setStateAction(state))
      ) {
        window.location.hash = hash;
      } else {
        window.history.back();
      }
    },
  ];
}
oliver-tunebat commented 1 year ago

A problem with ValentinH solution occurs when spamming clicks really fast. It triggers a bunch of back navigations, potentially skipping many pages of history.

shonmorgun commented 1 year ago

You can use MUI useTheme and useMediaQuery hooks to track screen size: code

AfzalH commented 1 year ago

Make your own wrapper component with additional useEffect and callback to handle the history change e.g.:

import React, { useCallback, useEffect } from 'react';
import ButtonBase from '@material-ui/core/ButtonBase';
import Dialog from '@material-ui/core/Dialog';

type Props = {
  open: boolean;
  onClose: () => void;
};

export const MyDialog: React.FC<Props> = ({ open, onClose, children }) => {
  useEffect(() => {
    if (!open) return;
    window.history.pushState('forward', '', '#dialog');
    window.addEventListener('popstate', onClose);

    return () => {
      window.removeEventListener('popstate', onClose);
    };
  }, [open, onClose]);

  const onCloseModal = useCallback(() => {
    onClose();
    window.history.pushState(
      'forward',
      '',
      window.location.pathname + window.location.search
    );
  }, [onClose]);

  return (
    <Dialog open={open} onClose={onCloseModal}>
      <ButtonBase onClick={onCloseModal}>X</ButtonBase>
      {children}
    </Dialog>
  );
};
razorch commented 1 year ago

Make your own wrapper component with additional useEffect and callback to handle the history change e.g.:

import React, { useCallback, useEffect } from 'react';
import ButtonBase from '@material-ui/core/ButtonBase';
import Dialog from '@material-ui/core/Dialog';

type Props = {
  open: boolean;
  onClose: () => void;
};

export const MyDialog: React.FC<Props> = ({ open, onClose, children }) => {
  useEffect(() => {
    if (!open) return;
    window.history.pushState('forward', '', '#dialog');
    window.addEventListener('popstate', onClose);

    return () => {
      window.removeEventListener('popstate', onClose);
    };
  }, [open, onClose]);

  const onCloseModal = useCallback(() => {
    onClose();
    window.history.pushState(
      'forward',
      '',
      window.location.pathname + window.location.search
    );
  }, [onClose]);

  return (
    <Dialog open={open} onClose={onCloseModal}>
      <ButtonBase onClick={onCloseModal}>X</ButtonBase>
      {children}
    </Dialog>
  );
};

This one is something, but the history is being saved actually. So if you open and close the dialog several times, then you will go through all this history when clicking browser back button. Also, I think you need to use onCloseModal function for popstate listener, not onClose.

TamirCode commented 6 months ago
 onClose();

what does onClose() function do here? Can someone post a complete example please