Компонент монтируется только один раз на экране вкладки?

#react-native

#react-native

Вопрос:

У меня есть приложение react native с 5 вкладками. Пятая вкладка — это экран профиля. Вот код вкладки профиля, я удалил много материала, потому что он фокусируется на компоненте, который монтировался:

 class Profile extends React.Component {

constructor(props) {
    super(props)
    this.state = {
        user: this.props.user,
        bio: "",
        storage_image_uri: '',
        postCount: 0,
        followerCount: 0,
        followingCount: 0,
        isLoading: true,
        navigation: this.props.navigation,
        userUID: Firebase.auth().currentUser.uid,
    }

    this.firestoreRef = 
    Firebase.firestore()
    .collection('posts')
    .doc(this.state.userUID)
    .collection('posts')
    .orderBy("date_created", "desc");
    
}

componentDidMount() {
    console.log("querying the db again")
    this.pullUserInfo()
    this.unsubscribe = this.firestoreRef.onSnapshot(this.getCollection);
}

componentWillUnmount(){
    this.unsubscribe();
}

pullUserInfo = async() => {
    await Firebase.firestore()
    .collection('users')
    .doc(this.state.userUID)
    .get()
    .then(function(doc){
        if (doc.exists) {
            this.setState({
                postCount: doc.data().postCount,
                followerCount: doc.data().followerCount,
                followingCount: doc.data().followingCount,
                storage_image_uri: doc.data().profilePic,
                bio: doc.data().bio,
                isLoading: false
            })
        } else {
            console.log("No such document!");
        }
    }.bind(this))
} 

gotToSettings() {
    this.state.navigation.navigate('Settings')
}

renderListHeader = () => {
    ... deleted, just rendering this information
}

render() {

    const { navigation } = this.props;
    const renderItem = ({ item }) => (

        <CurrentUserPostCell 
            ..deleted, unnecessary for this question
        />
    );

    if(this.state.isLoading){
        return(
          <View styles = {styles.container}>
            <ActivityIndicator size="large" color="#9E9E9E"/>
          </View>
        )
    }    
    return (
        <View>
            <FlatList
                data={this.state.userPostsArray}
                renderItem={renderItem}
                keyExtractor={item => item.key}
                ListHeaderComponent={this.renderListHeader}
                contentContainerStyle={{ paddingBottom: 50 }}
                showsHorizontalScrollIndicator={false}
                showsVerticalScrollIndicator={false}
            />
        </View>   
    )
}
}
 

Однако, когда я создаю новую запись, подписываюсь на нового пользователя или даже меняю биографию на странице настроек, компонент больше не запрашивает базу данных. Я добавил console.log («повторный запрос к БД») в componentDidMount(), но он печатается только при первом нажатии на вкладку профиля и никогда больше.

Я пробовал componentDidUpdate(), но это тоже ничего не исправляет. Как я могу решить проблему, связанную с тем, что компонент монтируется только при первом нажатии на вкладку и никогда больше?

РЕДАКТИРОВАТЬ: добавлена страница настроек по запросу

 class Settings extends React.Component {

    constructor(props) {
        super(props)
        this.state = {
            user: this.props.user,
            oldBio: "",
            newBio: "",
            profilePic: "",
            isLoading: false,
        }
        
    }

    componentDidMount() {
        this.pullBio()
    }
    
    
    //Pull bio from the db, save it to state
    pullBio = async() => {
        await Firebase.firestore()
        .collection('users')
        .doc(Firebase.auth().currentUser.uid)
        .get()
        .then(function(doc) {
            if (doc.exists) {
                this.setState ({
                    oldBio: doc.data().bio
                })
            } else {
                // doc.data() will be undefined in this case
                    console.log("No such document!");
            }
        }.bind(this));
    }

    changeBio = async() => {
        this.setState({ isLoading: true })
        // This should take us to the right place, adding a temp uid where we need it
        await Firebase.firestore()
        .collection('users')
        .doc(Firebase.auth().currentUser.uid)
        .set({
            bio: this.state.newBio
        }, { merge: true })
        .then(() => this.setState ({ 
            oldBio: this.state.newBio,
            isLoading: false
        }))
        .catch(function(error) {
            console.error("Error storing and retrieving image url: ", error);
        });
    }

    render() {
        if(this.state.isLoading){
            return(
              <View styles = {styles.container}>
                <ActivityIndicator size="large" color="#9E9E9E"/>
              </View>
            )
        }    
        return (
            <View style={styles.container}>
                <TouchableOpacity 
                    onPress={this.logOut}>
                    <Text>Sign out 🤷{this.state.user.username}</Text>
                </TouchableOpacity>

                <TextInput
                    style={styles.inputBox}
                    value={this.state.newBio}
                    onChangeText={newBio => this.setState({ newBio })}
                    placeholder={this.state.oldBio}
                    autoCapitalize='none'
                />
                <TouchableOpacity onPress={() => { this.changeBio() }}> 
                    <Text>Change Bio</Text>    
                </TouchableOpacity>

                
            </View>
        )
    }
}
 

Ответ №1:

Я дам вам ответ более высокого уровня, который должен помочь вам решить вашу проблему надлежащим образом

Как я могу решить проблему, связанную с тем, что компонент монтируется только при первом нажатии на вкладку и никогда больше?

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

Что вы должны делать?

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

С Firebase все намного проще, потому что вам даже не нужно вручную решать, когда пришло время для обновления. onSnapshot предоставляет вам прослушиватель, и сама Firebase будет вызывать его при каждом изменении пользовательских данных. Итак, все, что вам нужно сделать, это:

  1. при монтировании настройте lisetner с помощью onSnapshot
  2. всякий раз, когда это срабатывает, это означает, что данные изменились, поэтому просто получите нужные вам данные и обновите их в локальном состоянии (поскольку именно здесь вы их храните)
  3. когда компонент размонтируется, отпишитесь от прослушивателя

Что я могу сделать, чтобы componentDidMount запускался всякий раз, когда я нажимаю на вкладку профиля, которая запрашивает новые данные из firebase Firestore и обновляет информацию на вкладке профиля

Аналогично, это также будет излишним, потому что этот прослушиватель будет следить за тем, чтобы у вас всегда были самые последние данные, и вам не нужно будет обновлять их при нажатии на вкладку профиля. Но даже если Firebase не смогла создать для вас прослушиватели, вам не нужно запускать повторное монтирование компонента каждый раз, когда вы нажимаете на значок профиля, только для обновления данных, потому что вы можете просто обновлять данные каждый раз, когда нажимаете на значок, без повторного монтирования

Комментарии:

1. Спасибо, Макс! Я думал, что уже настраиваю прослушиватель при монтировании, и данные сохраняются в состоянии, но, похоже, я не отказываюсь от подписки.

Ответ №2:

Firestore get извлекает данные один раз. Возможно, вы захотите попробовать onSnapshot метод, который будет прослушивать любые изменения в вашем документе.

 firebase.firestore.collection('users').doc(userId)
    .onSnapshot(function(doc){
     //...
})
 

Надеюсь, это должно сработать даже без componentDidUpdate, поскольку вы уже запускаете обновление состояния в componentDidMount.

Подробнее читайте здесь.

Комментарии:

1. Привет! к сожалению, это не решило мою проблему. Изменение вызова Firestore на onSnapshot в pullUserData не устранило проблему: (

2. Вы уверены? Если вы настраиваете прослушиватель и изменяете данные в прослушиваемом пути, onSnapshot должно произойти событие. Обратите внимание, что это не будет запускаться componentDidMount снова, он будет запускать только обработчик. Попробуйте установить консоль. зарегистрируйте вызов внутри onSnapshot и посмотрите, что произойдет.

3. Возможно, я неправильно настраиваю прослушиватель. Функция pullUserInfo() — это то, как я запрашиваю информацию о профиле Firestore, если вы посмотрите на приведенный выше код. Возможно, вы можете добавить пример того, как я могу правильно добавить слушателя так, как вы имеете в виду? @ThalesKenne

4. @ThalesKenne хорошо, я добавил console.log(«получение информации») в оператор onSnapshot() if в pullUserData. Не печатался, за исключением одного раза, так же, как компонент монтировался

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

Ответ №3:

Когда вы переходите от настройки экрана к профилю экрана, профиль монтируется, затем вызывается componentDidMount профиля.

После этого, когда вы переходите на другой экран, профиль все еще находится на экране стека навигации, тогда componentDidMount профиля больше никогда не будет вызываться

Итак, проблема в том, что экран внутри панели вкладок не монтируется повторно.

Вы можете добавить это в конфигурацию вкладки:

 unmountOnBlur: true
 

Ссылка на документы: здесь

Другое хитрое решение:

Добавьте прослушиватель событий, чтобы проверить, сфокусирован ли экран профиля или нет. Если профиль сфокусирован, извлеките свои данные еще раз.

Ссылка: здесь

Комментарии:

1. Добавление размонтирования не сработало! попробует прослушиватель событий