Passing refs from Component to Component in React

Viewed 818

So I am not sure if passing refs would be the best thing to do but it's kinda what I have set-out to do tell me if there is a better option..

So I am trying to have an onClick of a nav link, scroll down to the the div "contactForm".

App.js

import ContactForm from './components/ContactForm'
import ParllaxPage from './components/ParllaxPage'
import NavigationBar from './components/NavigationBar'
import React from 'react';
import './App.css';

const App = () => {


  return (

    < div cssClass="App" >
      <body>
        <span><NavigationBar /></span>
        <ParllaxPage cssClass="parallax-wrapper" />
        <ParllaxPage cssClass="parallax-wrapper parallax-pageOne" />
        <ContactForm />
      </body >
    </div >
  );
}

export default App;

I was trying to use forwardRef but I am not sure that I was doing it correctly so...

NavigationBar.js

import ContactForm from "./ContactForm";
import React, { useRef } from "react";
import App from "../App";
import { Nav, Navbar, Form, FormControl, Button } from "react-bootstrap";

const ContactFormRef = React.forwardRef((props, ref) => (
 <ContactForm className="contactForm" ref={ref}>
  {props.children}
 </ContactForm>
));

const scrollToRef = (ref) => ref.current.scrollIntoView({ behavior: "smooth" });

const NavigationBar = () => {
 const ref = React.forwardRef(ContactFormRef);

 return (
  <Navbar bg="light" expand="lg">
   <Navbar.Brand href="#home">A1 Gutters</Navbar.Brand>
   <Navbar.Toggle aria-controls="b casic-navbar-nav" />
   <Nav className="mr-auto">
    <Nav.Link href="#home">Home</Nav.Link>
    <Nav.Link href="#link">Link</Nav.Link>
    <Nav.Link href="#" onClick={console.log(ref)}>
     Contact
    </Nav.Link>
   </Nav>
  </Navbar>
 );
};

export default NavigationBar;

I don't think the other files really need to be shown, I am just trying to get the className out of the ContactForm component so I can scroll to it onClick.. I currently just have a console.log in the onClick.

4 Answers

Using Hooks will simplify here.

  1. Have state variable for gotoContact and ref for contactRef
  2. Add click handler for navigation link contact
  3. Add useEffect hook and when ever use click on contact and ref is available (value in ref.current) then call the scroll to view)
import ContactForm from "./components/ContactForm";
import ParllaxPage from "./components/ParllaxPage";
import React, { useState, useEffect, useRef } from "react";
import "./App.css";

const NavigationBar = ({ onClickContact }) => {
  return (
    <Navbar bg="light" expand="lg">
      <Navbar.Brand href="#home">A1 Gutters</Navbar.Brand>
      <Navbar.Toggle aria-controls="b casic-navbar-nav" />
      <Nav className="mr-auto">
        <Nav.Link href="#home">Home</Nav.Link>
        <Nav.Link href="#link">Link</Nav.Link>
        <Nav.Link href="#" onClick={() => onClickContact()}>
          Contact
        </Nav.Link>
      </Nav>
    </Navbar>
  );
};

const App = () => {
  const [gotoContact, setGotoContact] = useState(false);
  const contactRef = useRef(null);

  useEffect(() => {
    if (gotoContact && contactRef.current) {
      contactRef.current.scrollIntoView();
      setGotoContact(false);
    }
  }, [gotoContact, contactRef.current]);

  return (
    <div cssClass="App">
      <body>
        <span>
          <NavigationBar onClickContact={() => setGotoContact(true)} />
        </span>
        <ParllaxPage cssClass="parallax-wrapper" />
        <ParllaxPage cssClass="parallax-wrapper parallax-pageOne" />
        <div ref={contactRef}>
          <ContactForm />
        </div>
      </body>
    </div>
  );
};

export default App;

You should identify the div "contactForm" with an id and have an anchor tag point to it:

<a href="#contactForm"></a>
<div id="contactForm"></div>

You can add scroll-behaviour: smooth to the body in CSS

No need to create a separate ContactFormRef wrapper. Simply use React.forwardRef in ContactForm itself. Those not passing a ref will not have to know it forwards refs.

Then, remember to further pass the ref received to a native element or use useImperativeHandle hook to add methods to it without passing it further down.

 const ref = React.forwardRef(ContactFormRef)

This is wrong.

You should do it the same as with native components:

const ref = useRef()

return <ContactForm ref={ref} >
    // etc
</ContactForm>

You are not rendering the ContactFormRef, so the reference points no nothing!

App.js should be like:

...
const App = () => {
  const myNestedRefRef=React.useRef();
  return (
    ...
      <NavigationBar contactRef={myNestedRefRef}/>
    ...
      <ContactForm ref={myNestedRefRef} />
    ...
  );
}
...

ContactForm.js

...
function ContactForm=React.forwardRef((props, ref) => (
 <form ref={ref}>
  ...
 </form>
));

NavigationBar.js

const NavigationBar = ({contactRef}) => {
 return (
  ...
    <Nav.Link href="#" onClick={console.log(contactRef)}>
  ...
 );
};

Consider that

If the <ContactForm/> hasn't been rendered yet, the ref will look like {current:null}

Related