RuntimeError
RuntimeError

Reputation: 1390

React: rendering a variable set in useEffect() is always one step behind

Working on an application for Android using React Native, I stumbled upon a strange issue.

There is a SearchBar to perform a name search on a database. The result should be rendered. In the code below, you see I set the variable with the result in useEffect()-Hook after defining the variable outside using useRef(): let returnValue = useRef('No results');

So I expected that if I type a name, such as "Mike", the database result (stored in returnValue.current) will be rendered immediately after submitting.

But no.

Actually, the result "Mike" is rendered after I open the Search Bar again and delete the last character, leaving "Mik". Then the application renders "Mike", as if it was exactly one step behind.

After searching for the issue, I just found async "problems" with useState()-Hook, but not with useEffect(). When I do console.log() the useState(), everything is set correctly. Even inside useEffect()- logging returnValue.current always gives the correct result in the correct time. The one single issue is the rendering of returnValue.current with that strange delay.

Is someone able to demystify this behavior?

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>
  );
};

Upvotes: 0

Views: 1211

Answers (1)

Alexander Staroselsky
Alexander Staroselsky

Reputation: 38787

Try replacing the useRef hook with just useState hook. It should be enough as returnValue is just a computed value derived from 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>
  );
};

Upvotes: 1

Related Questions