How to create a ref to child component, store it in context and pass it to a sibling in React with Material UI

464 views Asked by At

I have a component that looks like this:

import React from 'react';
import { Dialog, DialogContent, DialogTitle, Typography } from '@material-ui/core';

const FormDialog = ({ formId, onClose, onSubmit }: Props) => (
  <Dialog open disableBackdropClick>
    <DialogTitle>
      Create New Form
      <br />
      <Typography
        color="textSecondary"
        variant="caption"
      >
        Start a new form by filling in some starter info.
      </Typography>
    </DialogTitle>
    <DialogContent dividers>
      <FormContainer
        formId={formId}
        onSubmit={onSubmit}
        onClose={onClose}
      />
    </DialogContent>
  </Dialog>
);

export default FormDialog;

In a deeply nested component further down the line, I want to lock scrolling on this Dialog when a Popper (using Material UI Popper not popper.js) is open. I was able to do this with the vanilla DOM API, and found that the component I need a reference to is DialogContent. (The wheel event isn't bubbling up to Dialog).

How do I create a ref pointing to DialogContent and send it as a Context.Provider value over FormContainer?

Open to threading it as a prop too, but guessing if one is possible so is the other?

I looked into forwardRef and it doesn't seem like that would make sense here, but I could also be getting discouraged by Typescript. I was also thinking about useRef(null) inside the component, passing the variable output as a ref prop to DialogContent, catching ref.current in a useEffect, saving it with useState, and passing it to FormContainer but don't know if that would work / hoping that is not the solution!

1

There are 1 answers

1
Erfan On

It would be helpful if you showed where and how you have implemented Popper, but i hope this gives you some ideas:

Create a context and wrap all FormDialog children inside it.

Then create a state in FormDialog, that points to the status of the popper.

Pass both state, and setState functions down to wherever the Popper is located.

FormDialog

const FormDialogContext=createContext(null)

const FormDialog = ({ formId, onClose, onSubmit }: Props) => (

  const [isPopperOpen,setPopperOpen]=useState(false)

  <FormDialogContext.Provider value={[isPopperOpen,setPopperOpen]}>
   <Dialog open disableBackdropClick>
    <DialogTitle>
      Create New Form
      <br />
      <Typography
        color="textSecondary"
        variant="caption"
      >
        Start a new form by filling in some starter info.
      </Typography>
    </DialogTitle>
    <DialogContent dividers>
      <FormContainer
        formId={formId}
        onSubmit={onSubmit}
        onClose={onClose}
      />
    </DialogContent>
  </Dialog>
</FormDialogContext.Provider>
);

export default FormDialog;

Now, in any component where your Popper is located

 const [isPopperOpen,setPopperOpen]=useContext(FormDialogContext)

return (<>
         <Button 
          onClick={()=>setPopperOpen(prevState=>!prevState)}>
          Toggle Popper
          </Button>
          <Popper 
          open={isPopperOpen}
         ..................

Now you have total control over the status of Popper within FormDialog