Реакция: рендеринг переменной, установленной в useEffect(), всегда на один шаг позади

Работая над приложением для Android с использованием React Native, я наткнулся на странную проблему.

Существует SearchBar для выполнения поиска имени в базе данных. Результат должен быть отрендерен. В приведенном ниже коде вы видите, что я установил переменную с результатом в useEffect()-Hook после определения переменной снаружи с помощью useRef(): let returnValue = useRef('No results');

Поэтому я ожидал, что если я наберу имя, например «Майк», результат базы данных (хранящийся в returnValue.current) будет отображаться сразу после отправки.

Но нет.

На самом деле результат «Майк» отображается после того, как я снова открою панель поиска и удалю последний символ, оставив «Мик». Затем приложение отрисовывает «Майка», как если бы он отставал ровно на один шаг.

После поиска проблемы я только что нашел асинхронные «проблемы» с useState()-Hook, но не с useEffect(). Когда я делаю console.log() useState(), все устанавливается правильно. Даже внутри useEffect()- регистрация returnValue.current всегда дает правильный результат в нужное время. Единственная проблема — рендеринг returnValue.current с такой странной задержкой.

Кто-нибудь может демистифицировать это поведение?

import ...

const SearchBar = props => {
  let [inputValue, setInputValue] = useState(null);
  let returnValue = useRef('No results');

  const sendInputValueToReduxStore = text => {
    setInputValue(text);
    props.setInputValueSearchBar(text);
  };

  useEffect(() => {
    // setting database schema
    const personsSchema = {
      name: 'realm',
      properties: {
        name: 'string?',
        color: 'string?',
      },
    };

    // open the database
    let database = new Realm({
      path: fs.DocumentDirectoryPath + '/default.realm',
      schema: [personsSchema ],
      readOnly: true,
    });

    // doing the database query
    const allPersons = database.objects('realm');
    let resultArray = [];
    const query = inputValue;
    const queryResult = inputValue
      ? allPersons.filtered("name == '" + query + "'")
      : 'default';
    resultArray = Array.from(queryResult);
    const personNamesArray = resultArray.map(value => value.name);
    const personNames = personNamesArray.toString();
    returnValue.current = personNames;
    // logging always correct
    console.log('person name: ', personNames);
  }, [inputValue]);

  const isText = props.text;

  return (
    <View>
      <Header searchBar rounded>
        <Item>
          <Icon name="ios-search" />
          <Input
            placeholder="Search"
            onChangeText={text => sendInputValueToReduxStore(text)}
            value={inputValue}
          />
        </Item>
      </Header>
      {isText && (
        <View>
          <Text>{props.text}</Text>
        </View>
      )}
      //returnValue.current is rendered with delay
      {returnValue && <Text>{returnValue.current}</Text>}
    </View>
  );
};

person RuntimeError    schedule 09.02.2020    source источник
comment
Можете ли вы пояснить, почему вы используете useRef в этой ситуации? Почему бы просто не использовать дополнительный useState для установки значения вычисляемых лиц? Также вы можете избегать установления нового соединения с базой данных внутри каждой итерации useEffect.   -  person Alexander Staroselsky    schedule 09.02.2020
comment
@AlexanderStaroselsky Спасибо за ваш ответ. Я использую useRef(), потому что, когда я назначаю переменную с помощью useState(), выдается следующая ошибка: Присваивания переменной returnValue изнутри React Hook useEffect будут потеряны после каждого рендеринга. Чтобы сохранить значение с течением времени, сохраните его в хуке useRef и сохраните изменяемое значение в свойстве .current. --› Поэтому я сделал то, что рекомендуется в этом сообщении, и использовал useRef().   -  person RuntimeError    schedule 09.02.2020
comment
Правильно, если вы попытаетесь сохранить в переменную, это даст эту ошибку, я имею в виду полную замену returnValue новым хуком useState для получения возвращаемого значения настройки, как вы делаете с inputValue. const [returnValue, setReturnValue] = useState([]); // ... setReturnValue(personNames);   -  person Alexander Staroselsky    schedule 09.02.2020
comment
@AlexanderStaroselsky Может у вас есть пример для бедного новичка? ^^ Мне кажется, я не совсем понимаю, что вы имеете в виду.   -  person RuntimeError    schedule 09.02.2020
comment
Вот надуманный пример: stackblitz.com/edit/react-sntucz. Я просто имею в виду замену useRef на useState и установку значения returnValue внутри useEffect после того, как вы запросите/фильтруете/обработаете людей.   -  person Alexander Staroselsky    schedule 09.02.2020
comment
Рад слышать. Я добавил ответ, чтобы обрисовать в общих чертах возможный подход.   -  person Alexander Staroselsky    schedule 09.02.2020


Ответы (1)


Попробуйте заменить хук useRef только хуком useState. Этого должно быть достаточно, поскольку returnValue — это просто вычисленное значение, полученное из inputValue:

import ...

const SearchBar = props => {
  let [inputValue, setInputValue] = useState(null);
  let [returnValue, setReturnValue] = useState(null);

  const sendInputValueToReduxStore = text => {
    setInputValue(text);
    props.setInputValueSearchBar(text);
  };

  useEffect(() => {
    // setting database schema
    const personsSchema = {
      name: 'realm',
      properties: {
        name: 'string?',
        color: 'string?',
      },
    };

    // open the database
    let database = new Realm({
      path: fs.DocumentDirectoryPath + '/default.realm',
      schema: [personsSchema ],
      readOnly: true,
    });

    // doing the database query
    const allPersons = database.objects('realm');
    let resultArray = [];
    const query = inputValue;
    const queryResult = inputValue
      ? allPersons.filtered("name == '" + query + "'")
      : 'default';
    resultArray = Array.from(queryResult);
    const personNamesArray = resultArray.map(value => value.name);
    const personNames = personNamesArray.toString();
    setReturnValue(personNames);
  }, [inputValue]);

  const isText = props.text;

  return (
    <View>
      <Header searchBar rounded>
        <Item>
          <Icon name="ios-search" />
          <Input
            placeholder="Search"
            onChangeText={text => sendInputValueToReduxStore(text)}
            value={inputValue}
          />
        </Item>
      </Header>
      {isText && (
        <View>
          <Text>{props.text}</Text>
        </View>
      )}
      {returnValue && <Text>{returnValue}</Text>}
    </View>
  );
};
person Alexander Staroselsky    schedule 09.02.2020