Бесконечный цикл в React Native из-за useState

#reactjs #react-native #infinite-loop #use-state

#reactjs #react-native #бесконечный цикл #use-state

Вопрос:

У меня есть 1 / бесконечный цикл, использующий useState для сохранения данных из запроса axios. Я хочу ввести значение в представление во время сопоставления :

 import { StyleSheet, Text, View, TextInput, FlatList, Button, ScrollView } from 'react-native';
import styles from './styles'
import React, { useEffect, useState, Component } from 'react';
import App, { currentCityCode } from './app'
import axios from 'axios';
import { Dropdown } from 'react-native-material-dropdown';

function Chat(props) {
    useEffect(() => {
        getMessage()
    }, []
    );

    const messApiRest = "https://..."
    const [lastMessage, setLastMessage] = useState("")
    const [user, setUser] = useState("")
    const [categoryMessage, setcategoryMessage] = useState("")
    const [idUser, setIdUser] = useState("")
    const [allMessages, setAllMessages] = useState([])
    const [messageOneByOne, seMessageOneByOne] = useState([])

    function getMessage() {
        axios.get(messApiRest   "/api/messages",
            {
                headers: {
                    'Authorization': "Bearer "   props.token,
                },
                params: {
                    'citycode': props.cityCode
                },
            })
            .then(response => {
                setAllMessages(response.data.results)

            },
            )
    }

    function getUserName(toto) {

        axios.get(messApiRest   "/api/users/"   toto,
            {
                headers: {
                    'Authorization': "Bearer "   props.token,
                }
            }
        )
            .then(response => {
                console.log(response.data.username)
                setUser(response.data.username)
            })
    }

    return (
        <View style={styles.container}>

            {allMessages.map(function (message) {
                getUserName(message.author.id)

                return (
                    <Text>{user}: {message.content}</Text>
                )
            })}

        </View>
    )
}

export default Chat
  

Основная проблема заключается в том, что если сохранить мой {user}, используя useState, я создаю бесконечный цикл.

2 / Я пытался избежать этого, не сохраняя его с помощью useState и напрямую возвращая значение response.data.username в представлении, но это не работает. Я знаю, что не могу вернуть значение promise , поэтому вместо этого я использую useState .

Я новичок {вероятно, не очень хороший}, я пытаюсь понять, как работают React и axios, но я на пределе и не могу угадать решение…

Спасибо за вашу помощь, если кто-то осмелится ^^

Ответ №1:

Бесконечный цикл возникает из-за вызова getUserName возвращаемого JSX.

Вы можете попробовать выборку пользователей и сообщений внутри useEffect и обновлять состояние только тогда, когда они оба извлекаются:

 function Chat(props) {
  const messApiRest = "https://...";
  const [lastMessage, setLastMessage] = useState("");
  const [user, setUser] = useState("");
  const [categoryMessage, setcategoryMessage] = useState("");
  const [idUser, setIdUser] = useState("");
  const [allMessages, setAllMessages] = useState([]);
  const [messageOneByOne, seMessageOneByOne] = useState([]);

  useEffect(() => {
    getMessage()
      .then((res) => {
        return res.data.results;
      })
      .then((msgs) => {
        const userPromises = msgs.map((m) => getUserName(m.author.id));
        Promise.all(userPromises).then((users) => {
          setAllMessages(
            msgs.map((msg, index) => ({ ...msg, user: users[index] }))
          );
        });
      });
  }, []);

  function getMessage() {
    return axios.get(messApiRest   "/api/messages", {
      headers: {
        Authorization: "Bearer "   props.token,
      },
      params: {
        citycode: props.cityCode,
      },
    });
  }

  function getUserName(toto) {
    return axios.get(messApiRest   "/api/users/"   toto, {
      headers: {
        Authorization: "Bearer "   props.token,
      },
    });
  }

  return (
    <View style={styles.container}>
      {allMessages.map(function (message) {
        return (
          <Text>
            {message.user}: {message.content}
          </Text>
        );
      })}
    </View>
  );
}

export default Chat;
  

В этом примере я объединил user и messages в allMessages , но вы можете изменить его по своему усмотрению.

Ответ №2:

Основная проблема здесь заключается в том, что каждый вызов render getUserName , который, в свою очередь, устанавливает состояние, которое запускает другой рендеринг.

 render => getUserName => setUser => render => getUserName => setUser => render
  

Одним из решений было бы создание отдельного компонента username, который обрабатывает разрешение имен:

 const DisplayName = ({userid}) => {
    const [displayName, setDisplayName] = useState();
    useEffect(() => {
      // resolve username and call setDisplayName
    }, [userid]);

    return (
      <span>{displayName}</span>
    );
}
  

Затем вы можете передать идентификатор автора и позволить ему обработать его:

 return (
  <View style={styles.container}>
    {allMessages.map(message => (
        <Text>
          <DisplayName userid={message.author.id} />: {message.content}
        </Text>
      ))
    }
  </View>
)
  

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

Чтобы решить повторяющуюся выборку имени пользователя, вы можете либо кэшировать их где-то за пределами компонента (хранилище, a la redux), либо переместить его обратно в родительский компонент и разрешить все имена пользователей заранее:

 const [usernames, setUsernames] = useState({});

const resolveUsernames = useCallback(async authorIds => {
  // create an array of 'getUserName' promises and wait for them to all come back
  const usernames = await Promise.all(authorIds.map(id => getUserName(id)));

  // map the names back to the corresponding ids, e.g. { 123: 'alice', 456: 'bob'}
  // Promise.all results come back in the same order as the input promises, so you can associate them by index.
  const idMap = authorIds.reduce((ids, id, index) => ({
    ...ids,
    [id]: usernames[index]
  }), {});

  // store the id-to-name mapping in state:
  setUsernames(idMap);
}, [authorIds])


function getMessage() {
  axios.get(messApiRest   "/api/messages",
    {
      headers: { 'Authorization': "Bearer "   props.token },
      params: {  'citycode': props.cityCode },
    })
    .then(response => {
      const results = response.data.results;
      // this is just a shorthand way of getting an array of distinct ids
      const authorIds = [...new Set(results.map(message => message.author.id))];
      resolveUsernames(authorIds);
      setAllMessages(response.data.results)
    })
}
  

Установив это, вы можете искать имя пользователя для каждого сообщения из состояния компонента usernames :

 {allMessages.map(message => (
  <Text>
    {usernames[message.author.id]}: {message.content}
  </Text>
))
}
  

Ответ №3:

Спасибо за ваш ответ. Я пробовал оба, каждый решал некоторые проблемы, но не все.

Я мог бы быть немного более конкретным: я ищу более простой код, даже если мне придется переосмыслить логику моего подхода.

Вот что я хочу сделать :

1 / Доступ к API с помощью axios для получения данных всех сообщений, размещенных в определенной области. Эти данные дают мне содержимое сообщения, которое я хочу отобразить, и идентификатор пользователя.

2 / Я хочу использовать идентификатор пользователя для выполнения второго вызова API, чтобы на этот раз получить имя пользователя, чтобы отобразить его перед содержимым сообщения.

Основные подходы, которые я выбрал: A / I может сохранять как содержимое сообщений, так и имя пользователя в разных массивах и сопоставлять их параллельно для отображения данных. B / I может вызывать API только для получения всех данных сообщений, складирования их в массив, затем сопоставления массива для отображения содержимого сообщения и выполнения в это время второго вызова API для извлечения имени пользователя из его идентификатора. Это решение, которое я пробовал.

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