Как преобразовать компонент класса React в компонент функции с помощью ловушек для получения данных Firebase - PullRequest
0 голосов
/ 01 апреля 2019

У меня есть рабочий компонент класса React, который я хочу преобразовать в функциональный компонент, чтобы использовать хуки для состояния и т. Д. Я изучаю хуки React. Версия компонента класса работает нормально, функциональный компонент - то, где мне нужна помощь.

Структура данных состоит из списка клиентов с тремя «клиентами». Изображение этого здесь:

enter image description here

Все, что я пытаюсь сделать, это получить эти данные, перебрать их и отобразить данные каждого ключа имени для пользователя. Достаточно просто.

Проблема в том, что вызов firebase от моего компонента приводит к ошибочному поведению, так как данные не извлекаются правильно. Последнее имя клиента постоянно вызывается, и оно зависает в браузере. :)

Вот изображение результата:

enter image description here

Вот код:



import React, {Component,useContext,useEffect, useState} from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import firebase from 'firebase/app';
import {Consumer,Context} from '../../PageComponents/Context';


const styles = theme => ({
    root: {
        flexGrow: 1,
    },
    paper: {
        padding: theme.spacing.unit * 2,
        textAlign: 'center',
        color: theme.palette.text.secondary,
    },
});


const FetchData = (props) =>{
    const [state, setState] = useState(["hi there"]);
    const userID = useContext(Context).userID;

    useEffect(() => {
        let clientsRef = firebase.database().ref('clients');
        clientsRef.on('child_added', snapshot => {
            const client = snapshot.val();
            client.key = snapshot.key;
            setState([...state, client])
         });
    });

    //____________________________________________________BEGIN NOTE: I am emulating this code from my class component and trying to integrate it

    // this.clientsRef.on('child_added', snapshot => {
    //     const client = snapshot.val();
    //     client.key = snapshot.key;
    //     this.setState({ clients: [...this.state.clients, client]})
    //  });

    //___________________________________________________END NOTE


     console.log(state)

        return (

            <ul>

                {
                    state.map((val,index)=>{

                        return <a key={index} > <li>{val.name}</li>   </a>

                    }) 
                }

            </ul>
        )


}  


FetchData.propTypes = {
  classes: PropTypes.object.isRequired
}

export default withStyles(styles)(FetchData)

Ответы [ 3 ]

3 голосов
/ 01 апреля 2019

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

Вместо этого вам нужно установить прослушиватель один раз после монтирования компонента, вы можете сделать это, предоставив пустой массив зависимостей ([]) в качестве второго аргумента useEffect:

useEffect(() => {
  // your code here
}, []) // an empty array as a second argument

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

Но есть еще один важный момент. Поскольку вы настраиваете слушателя, вам нужно очистить его, когда он вам больше не нужен. Это делается с помощью другого обратного вызова, который вы должны вернуть в функции, которую вы передаете useEffect:

useEffect(() => {
  let clientsRef = firebase.database().ref('clients');
  clientsRef.on('child_added', snapshot => {
    const client = snapshot.val();
    client.key = snapshot.key;
    setState([...state, client])
  });
  return () => clientsRef.off('child_added') // unsubscribe on component unmount
}, []);

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

2 голосов
/ 01 апреля 2019

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

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

    useEffect(() => {
        let clientsRef = firebase.database().ref('clients');
        clientsRef.on('child_added', snapshot => {
            const client = snapshot.val();
            client.key = snapshot.key;
            setState([...state, client])
         });
    }, []);//An empty array here means  this will run only once.

Массив сообщает, какие свойства нужно посмотреть. Всякий раз, когда одно из этих свойств изменяется, оно запускает функцию очистки и повторно запускает эффект. Если вы отправите пустой массив, он будет запущен только один раз (поскольку нет свойств для просмотра). Например, если вы добавите [userId], эффект будет запускаться каждый раз, когда изменяется переменная userId.

Говоря о функции очистки, вы не возвращаете ее в свой эффект-хук. Я не достаточно знаком с firebase, чтобы знать, нужно ли что-либо очищать при уничтожении компонента (например, удалить привязку события child_added). Хорошей практикой будет возвращать метод как последнюю часть вашего эффекта использования. Окончательный код будет выглядеть примерно так:

 useEffect(() => {
        let clientsRef = firebase.database().ref('clients');
        clientsRef.on('child_added', snapshot => {
            const client = snapshot.val();
            client.key = snapshot.key;
            setState([...state, client])
         });
        return () => { /*  CLEANUP CODE HERE */ };
    }, []);//An empty array here means  this will run only once.
2 голосов
/ 01 апреля 2019

Эффекты по умолчанию запускаются после каждого рендера, а установка состояния вызывает рендеринг. Любой эффект, который обновляет состояние , нуждается в , чтобы указать массив зависимостей , иначе у вас просто будет бесконечный цикл update-render-update-render.

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

Затем убедитесь, что используется форма функции обновления состояния , чтобы убедиться, что следующее состояние всегда зависит от текущего состояния, а не от того, какое значение закрытия было при связывании события. Попробуйте использовать useReducer, если состояние вашего компонента становится более сложным.

const [clients, setClients] = useState([])

useEffect(() => {
  const clientsRef = firebase.database().ref("clients")

  const handleChildAdded = (snapshot) => {
    const client = snapshot.val()
    client.key = snapshot.key
    setClients(clients => [...clients, client])
  }

  clientsRef.on("child_added", handleChildAdded)
  return () => clientsRef.off('child_added', handleChildAdded)
}, [])

Также см .:

...