简体   繁体   中英

React with Typescript functional components - how to call child method from parent

I have this simplified structure:

<Page>
  <Modal>
    <Form />
  </Modal>
</Page>

All of these are functional components. And in <Modal /> I have a close function that looks like this:

const close = () => {
  // apply a CSS class - so the modal disappears animatedly ..
  // then use setTimeout() to completely remove the modal after the animation ends ..
}

Do you have an idea how the <Page /> component can call the <Modal /> close method? And the page has to do it because this is where I'm doing the call to API with the data from the form, and so if all is OK with API request - close the modal.

(The <Form /> handles only the form validation but then passes the data to <Page /> where all the business logic happens.)

PS: The project uses Typescript ... so I have to deal with types as well :(

I look into your problem. I think my example should clarify your problem. Let me know if you have any questions.

import { ReactNode, useCallback, useEffect, useState } from 'react'
import { render } from 'react-dom'

function App() {
  return (
    <div>
      <Page />
    </div>
  )
}

function Page() {
  const [isModalOpen, setModalOpen] = useState(false)

  const handleFormSubmit = useCallback((formValues: FormValues) => {
    console.log({ formValues })
    setModalOpen(false)
  }, [])

  return (
    <div>
      <button onClick={() => setModalOpen(!isModalOpen)}>Toggle modal</button>

      <Modal isOpen={isModalOpen}>
        <Form onSubmit={handleFormSubmit} />
      </Modal>
    </div>
  )
}

interface ModalProps {
  isOpen: boolean
  children: ReactNode
}
function Modal(props: ModalProps) {
  const [isOpen, setIsOpen] = useState(false)

  const close = () => {
    setIsOpen(false)
  }

  const open = () => {
    setIsOpen(true)
  }

  useEffect(() => {
    if (!props.isOpen && isOpen) close()
    if (props.isOpen && !isOpen) open()
  }, [props.isOpen])

  if (!isOpen) return null

  return <div className="modal">{props.children}</div>
}

interface FormProps {
  onSubmit: (formValues: FormValues) => void
}
interface FormValues {
  username: string
  password: string
}
function Form(props: FormProps) {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')

  return (
    <form
      onSubmit={e => {
        e.preventDefault()

        props.onSubmit({
          username,
          password
        })
      }}
    >
      <input
        type="text"
        placeholder="username"
        onChange={e => {
          setUsername(e.target.value)
        }}
      />
      <input
        type="text"
        placeholder="password"
        onChange={e => {
          setPassword(e.target.value)
        }}
      />
      <button type="submit">Submit</button>
    </form>
  )
}

render(<App />, document.getElementById('root'))

I assumed you are fresh in FE or React world. Propably you do not need that much-nested structure.

There is a special hook in React called useImperativeHandle. You can use it to call child's functions from parent.

You can find out more in the oficcial React documentation .

example of usage

Child Component

Needs to be wrapped into forwardRef like that:

export const ChildComponent = forwardRef((props, ref) => {

useImperativeHandle(ref, () => ({
    async functionName() {
      await someLogic();
    },
  }));

Parent Component

In parent component you need to pass ref to the child. Then you can use child's function this way:

const childRef = useRef(null)

childRef.current.functionName()

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