Pull to refresh

React Native scroll to element

Original author: Viktor Chukhlov

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

Начинаем

Работая с React Native, иногда нам нужно найти способ прокрутки до определенного элемента. Это просто, когда scrollview и все необходимые элементы лежат в одном дереве DOM.

Представьте себе случай, у вас в приложении есть Drawer Navigation, по нажатию на определенную кнопку из бокового меню, приложение должно закрыть sidebar, перейти на нужный экран и перейти к определенному элементу. Мы понимаем, что кнопка из панели навигации, scrollview и необходимый элемент основаны на разных деревьях DOM.

Моё решение

class Refs {
  private _elements = new Map();

  public setRef = (key: string) => (ref: any) => {
    this._elements.set(key, ref);
  };

  public getRef(key: string) {
    return this._elements.get(key);
  }

  public remove(key: string) {
    this._elements.delete(key);
  }

  public clear() {
    this._elements.clear();
  }
}

export default new Refs();

Теперь у нас есть класс, в котором мы можем хранить ссылки на необходимые элементы и манипулировать ими. Если у вас есть вопросы по поводу моих решений, задавайте их в Telegram Channel!

Создадим Drawer Navigation и воспользуемся методом setRef

import React, {FC} from 'react';
import {View, SafeAreaView} from 'react-native';
import Refs from '../../../helpers/refs';
import {
  DrawerContentScrollView,
  DrawerContentComponentProps,
} from '@react-navigation/drawer';
import DrawerHeader from './drawerHeader';
import DrawerMenu from './drawerMenu';
import DrawerButtons from './drawerButtons';
import styles from './styles';

const DrawerContent: FC<DrawerContentComponentProps> = ({navigation}) => {
  Refs.setRef('navigation')(navigation);
  // Запишем ссылку на навигацию в обьект нашего класса

  return (
    <View style={styles.menu}>
      <DrawerContentScrollView>
        <DrawerHeader />
        <DrawerMenu />
      </DrawerContentScrollView>

      <SafeAreaView>
        <DrawerButtons />
      </SafeAreaView>
    </View>
  );
};

export default DrawerContent;

Создадим ScrollView и добавим ссылку на его Ref с помощью setRef

import React, {FC, useEffect} from 'react';
import {View, SafeAreaView, ScrollView} from 'react-native';
import {DrawerContentComponentProps} from '@react-navigation/drawer';
import Refs from '../../helpers/refs';
import Statistics from '../../components/statistics';
import Header from '../../components/header';
import Goals from '../../components/goals';
import Offers from '../../components/offers';
import styles from './styles';

const Main: FC<DrawerContentComponentProps> = ({navigation}) => {
  useEffect(() => {
    return () => Refs.remove('scroll'); // remove scroll on unmount 
  }, []);

  return (
    <SafeAreaView style={styles.body}>
      <ScrollView ref={Refs.setRef('scroll')}> // add scroll to Refs
        <View style={styles.statisticsBar} />
        <Statistics />
        <Header {...navigation} />
        <Goals />
        <Offers />
      </ScrollView>
    </SafeAreaView>
  );
};

export default Main;

Создадим компонент Offers и добавим ссылку на его Ref с помощью setRef

import React, {useEffect, useMemo, useContext} from 'react';
import {View} from 'react-native';
import Refs from '../../helpers/refs';
import {TabsContext} from '../../providers/tabs';
import Tabs from '../tabs';
import OffersList from './list';
import styles from './styles';

const OffersView = () => {
  const [tab] = useContext(TabsContext);

  useEffect(() => {
    return () => Refs.remove('offers'); // remove reference link 
  }, []);

  return (
    <>
      <Tabs />
      <View style={styles.offers} ref={Refs.setRef('offers')}> // add offers view element to Refs
        <OffersList />
      </View>
    </>
  );
};

export default OffersView;

Пишем метод scrollToOffers

import Refs from '../../../helpers/refs';

const scrollToOffers = () => {
  const scroll = Refs.getRef('scroll');
  const offers = Refs.getRef('offers');
  const navigation = Refs.getRef('navigation');

  offers?.measure((x: number, y: number) => {
    navigation?.jumpTo('Main');
    scroll?.scrollTo({x, y, animated: true});
  });
};

Обязательно почистим за собой Refs class

import React, {useEffect} from 'react';
import Refs from './helpers/refs';
import DrawerNavigation from './stack/drawer';

function App() {
  useEffect(() => {
    return () => Refs.clear(); // clear whole map from Refs class
  }, []);

  return (
     <DrawerNavigation />
  );
}

export default App;

Подведем итоги

Почему я не использовал HOC или хуки, чтобы закрыть эту задачу?

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

Подпишитесь на мой Telegram канал, здесь вы найдете предложения удаленной работы с высокими рейтами.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.