Как получить высоту функционального компонента без состояния, который является дочерним?

Я пытаюсь получить высоту дочернего компонента без состояния, чтобы я мог использовать его высоту в родительском классе, но получаю следующую ошибку: Invariant Violation: Stateless function components cannot have refs.

Упрощенный код

Родительский класс

class App extends React.Component {
  componentDidMount() {
    console.log(this.header);
  }
  render() {
    return (
      <Child ref={component => this.header = component} />
    )
  }
}

ребенок

const Child = () => (
  <header ref="header">
    Some text  
  </header>
)

Есть ли способ сделать это?

Вот ссылка на Codepen с ошибкой.

Обновить:

Актуальный код / ​​контекст

Итак, у меня есть компонент Header, который выглядит так:

export const Header = ({dateDetailsButton, title, logo, backButton, signUpButton, inboxButton, header}) => (
    <header header className="flex flex-row tc pa3 bb b--light-gray relative min-h-35 max-h-35">
      {signUpButton ? <Link to="/edit-profile-contact" href="#" className="flex z-2"><AddUser /></Link> : null }
      {backButton ? <BackButton className="flex z-2" /> : null }
      <h1 className={logo ? "w-100 tc dark-gray lh-solid f4 fw5 tk-reklame-script lh-solid ma0" : "w-100 tc dark-gray lh-solid f4 fw4 absolute left-0 right-0 top-0 bottom-0 maa h1-5 z-0"}>{title}</h1>
      {dateDetailsButton ? <Link to="/date-details" className="absolute link dark-gray right-1 top-0 bottom-0 maa h1-5 z-2">Details</Link> : null }
      {inboxButton ? <Link to="/inbox" href="#" className="flex mla z-2"><SpeechBubble /></Link> : null}
    </header>
)

В некоторых случаях я хочу добавить логику к этому заголовку, чтобы оживить его (например, на домашней странице, когда пользователь прокручивает, я анимирую заголовок, чтобы он стал фиксированным, когда он прокручивает определенную точку - липкий заголовок, если вы будете).

Способ, которым я делал это раньше, состоял в том, чтобы просто иметь отдельный класс для заголовка с определенной функциональностью (как описано выше) и один без. Но для того, чтобы мой код оставался СУХИМЫМ, я выделил Заголовок как его собственный функциональный компонент без сохранения состояния с целью обернуть его в Класс, который придает ему функциональность липкого заголовка.

Вот класс для этого:

export default class FeedHeader extends React.Component {

    constructor(props) {
        super(props);
        this.handleScroll = this.handleScroll.bind(this);
        this.state = {
            scrolledPastHeader: null,
            from: 0,
            to: 1,
        }
    }

    componentDidMount() {
        window.addEventListener('scroll', this.handleScroll);
        this.setState({navHeight: this.navHeight.offsetHeight});
    }

    componentWillUnmount() {
        window.removeEventListener('scroll', this.handleScroll);
    }

    handleScroll(e) {
        const { navHeight, scrolledPastHeader } = this.state;
        const wy = document.body.scrollTop;
        if (wy < navHeight) this.setState({scrolledPastHeader: null})
        else if (wy > navHeight && wy < 100) {
            this.setState({scrolledPastHeader: false})
        }
        else this.setState({scrolledPastHeader: true})
    }

    getMotionProps() {
        const { scrolledPastHeader } = this.state;

        return scrolledPastHeader === false ?
        {
            style: {
                value: this.state.from
            }
        }
        : {
            style: {
                value: spring(this.state.to)
            }
        }
    }

    render() {
        const { brand, blurb } = this.props;
        const { scrolledPastHeader } = this.state;
        return (
            <Motion {...this.getMotionProps()}>
                {({value}) => {
                    return (
                        <Header ref="child" title={this.props.title} backButton={false} signUpButton inboxButton logo/>
                    );
                }}
            </Motion>
        )
    }
}

Так что это контекст для этой конкретной проблемы - я надеюсь, что это делает вещи немного яснее.

Извините за отсутствие контекста, я предполагал, что ответ будет более прямым, чем кажется!

2 ответа

Решение

Итак, прежде всего, спасибо всем за их ответы - это очень ценится!

Я начал реализацию react-waypoints в соответствии с рекомендацией Win, хотя стало очевидно, что мне нужно будет немного изменить свой код, чтобы он заработал, поэтому я решил, что у меня будет последний поиск, чтобы попытаться найти альтернативное решение, используя refs,

Ответ, который я искал, на самом деле был довольно прост и пришел из следующей ветки GitHub от mnpenner.

Решение

Поскольку React не разрешает ссылки на функциональный компонент без сохранения состояния, вместо этого вы можете обернуть функциональный компонент в оболочку, предоставив оболочке свою собственную ref, вот так:

  render() {
    return (
      <div ref={el => {this.childWrap = el;}}>
        <Child />
      </div>
    )
  }

Затем вы можете получить доступ к высоте компонента, нацелив упаковку:

  componentDidMount() {
        if(this.childWrap && this.childWrap.firstChild) {
        let childHeight = this.childWrap.offsetHeight;
                console.log(childHeight);
    }
  }

Хотя это, возможно, и не самое элегантное решение, оно определенно решает мою проблему на данный момент.

Вот кодекс для справки.

Вот решение с использованием React-waypoint. Вы можете проверить это здесь: https://codesandbox.io/s/qp3ly687

Я добавил debounce в onEnter и onLeave, чтобы он не обновлял состояние как сумасшедший, поэтому мы можем оптимизировать производительность при прокрутке, и это не заблокирует приложение. Опять же, это очень грубое представление о том, что вы можете сделать, и есть множество вариантов для улучшения реализации.

=== Предыдущая

Это один из способов обойти проблему, сохранив компонент без сохранения состояния. Поскольку контекст не был объяснен далее, это должно дать вам возможность увидеть, как вы можете получить высоту дочернего компонента, который находится внутри компонента без состояния.

class App extends React.Component {
  componentDidMount() {
    console.log(this.child.offsetHeight);
  }
  render() {
    return (
      <div>
        <Wrapper>
          <div 
            ref={child => this.child = child}
            style={{height: 500}}>
            Some text!!
          </div>
         </Wrapper>
       </div>
    )
  }
}

const Wrapper = ({children}) => {
  return (
    <div>
      <header>Header</header>
      <div>{children}</div>
      <footer>Footer</footer>
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById("main")
);

Другие вопросы по тегам