React Hooks - Passing ref to child to use ScrollIntoView
Asked Answered
D

1

6

I have two components. A parent and a child.

Inside the parent component I have a button. If the user clicks on that button I want to do a ScrollIntoView to another button inside the child component.

I guess I want to define a reference to the childs button a so that I inside the parent button onClick can do a:

ref.scrollIntoView({block: 'end', behavior: 'smooth'});

that will scroll to the button in the child component.

Here is a minified example:

ParentComponent.jsx

import React, {useRef} from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = props => {
  const childReference = useRef(null);

  const onClick = () => {
    childReference.scrollIntoView({block: 'end', behavior: 'smooth'});
  }

  return (
    <>
      <...some other components>
      <Button onClick={onClick}>Click me to be forwarded</Button>
      <ChildComponent ref={childReference}/>
    </>
  );
};

ChildComponent.jsx

import React from 'react';

const ChildComponent = (props, ref) => {

  const { name, value, description } = props;

  return (
    <...some other components>
    <Button ref={ref}>You should be forwarded to me</Button>
  );
};

ChildComponent.propTypes = {
  name: PropTypes.string.isRequired,
  value: PropTypes.number,
  description: PropTypes.string,
};

ChildComponent.defaultProps = {
  value: 0,
  description: '',
};

export default React.forwardRef(ChildComponent);

I know the above code doesn't work, it was just to illustrate what I am trying to achieve.

I have really tried every other solution I have been able to find by Googling and they all seem so easy, but none of them seem to work for my use case. I have tried using forwardRef as well, but that also doesn't fix it for me.

UPDATE

I guess I was a little vague on what's not working. I've been getting a lot of different error messages with the implementation.

The following is one of them:

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Solution

Okay. I thought I'd assemble the pieces here with the solution provided by @Vencovsky.

This is the full implementation with the two example components seen in the question:

ParentComponent.jsx

import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = props => {
  const childReference = useRef(null);

  const scrollIntoView = () => {
    childReference.current.scrollIntoView({block: 'center', inline: 'center', behavior: 'smooth'});
  }

  return (
    <>
    <...some other component>
    <Button onClick={scrollIntoView}>Click me to be forwarded</Button>
    <ChildComponent ref={childReference}
    </>
  );
};

export default ParentComponent;

ChildComponent.jsx

import React, {forwardRef} from 'react';
import PropTypes from 'prop-types';

const ChildComponent = forwardRef((props, ref) => {
  const { name, value, description } = props;

  return(
    <>
      <...some other components>
      <Button ref={ref}>You should be forwarded to me</Button>
    </>
  );
});

ChildComponent.propTypes = {
  name: PropTypes.string.isRequired,
  value: PropTypes.number,
  description: PropTypes.string,
};

ChildComponent.defaultProps = {
  value: 0,
  description: '',
};

export default ChildComponent;
Druce answered 2/12, 2019 at 15:40 Comment(2)
It seems that your button is also a custom component. Are you sure the ref is being passed to the html button element inside there?Barehanded
@JeroenWienk. That is actually a good suggestion. I guess I should try with a basic HTML5 button first, and if that works I should of course update my custom Button as well.Druce
T
5

Edit 2:

I guess you are doing something like

const ChildComponent = (props, ref) => { ... }

ChildComponent.propTypes = { ... }

export default React.forwardRef(ChildComponent)

But what you need to do is pass propTypes after React.forwardRef, like so:

const ChildComponent = (props, ref) => { ... }          

const ForwardComponent = React.forwardRef(ChildComponent)

ForwardComponent.propTypes = { ... }

export default ForwardComponent

A better way to do it would be like

//                     using forwarRef
const ChildComponent = React.forwarRef((props, ref) => {

  const { name, value, description } = props;

  return (
    <...some other components>
    <Button ref={ref}>You should be forwarded to me</Button>
  );
});

Then you wouldn't need to change propTypes and create another component.

Edit:

As your Edit, I can see that you forgot to use React.forwardRef.

You should add

export default React.forwardRef(ChildComponent)

To your ChildComponent file (export it with forwardRef).


What is not working? Are you getting an error? You should explain better what is going on, but I will try to guess.

There is somethings that can make it not work.

  1. You need to use ref.current.foo instead of ref.foo
  2. As @JeroenWienk said:

    It seems that your button is also a custom component. Are you sure the ref is being passed to the html button element inside there?

  3. To use the second parameter of an functional component, you should be using React.forwardRef. e.g. export default React.forwardRef(ChildComponent)
Theda answered 2/12, 2019 at 17:37 Comment(10)
Thanks. This got me somewhat in the right direction. However I am now getting this warning: Warning: forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component? If I am not supposed to forwardRef a React component, how is it then meant to be used?Druce
@Druce Please add to your question how you are using propTypes/defaultProps with the forwardRef. I think I know how to solve this problem, but if you add that, I can be more certain. I will also edit my question, so please take a look after.Theda
I have updated my code, to resemble what I actually have implemented. The scrollIntoView works now, it would be a joy to get rid of the error.Druce
@Druce get rid of the error do you mean the warning? Just check my edit, now it should work with no error.Theda
Yes, I meant the warning. One minor thing is that I get ESLint errors for the props that I passed into my ChildComponent.Druce
Nevermind. I saw you update! :D Thanks. You're a lifesaver ;)Druce
I have 1 final question, which I think you might know the question to. As it was seen in my example I was using a custom button, which is basically just a styled materialUI button. If I were to add this implementation to that, would I then also need to wrap that in a forwardRef?Druce
@Druce I think it would be better if you create another question, didn't understand really well what you meant in your commment. ` If I were to add this implementation to that` is kind of confusingTheda
@Druce Try it to see if it works, if it doesn't work, ask a new question and explain what isn't workingTheda
Yes. I will. Thanks.Druce

© 2022 - 2024 — McMap. All rights reserved.