React Native useState sync

Viewed 171

I have a component TextInput with a state "text", that represents its value. Also, I have an other component UsernameTextInput which wraps the TextInput and contains a state "isUsernameAvailable", which indicates if the current value of the text input is allowed to be written in my database.

As useState is async, sometimes when the user types really fast, the isUsernameAvailable which correspond to the typed username is wrong. For example, the username "tarantino" is not available, but sometimes, with my current code, it is, because of useState is async and I am not handling it correctly...

Is there any way to make that sync?

Here is my current code:

function UsernameInput(props) {    
  const [isUsernameAvailable, setIsUsernameAvailable] = useState(undefined);

  const usernameInputRef = useRef(null);

  const previousValues = useRef(null);

  const checkUsername = async () => {
    const { firebase } = props;

    // Get the username
    const username = usernameInputRef.current.getText();

    
    // Check if the username is valid and available on the database
    const isUsernameAvailable = validateUsername(username) &&
                          (await firebase.isUsernameAvailable(username));

    setIsUsernameAvailable(isUsernameAvailable);
  };

  useEffect(() => {
    // Get the username from the text input
    const username = usernameInputRef.current.getText();

    if (
      previousValues.current?.username !== username &&
      previousValues.current?.isUsernameAvailable !== isUsernameAvailable
    ) {          
        console.log(`${username} - ${isUsernameAvailable}`)
        
        // Update previousValues reference
        previousValues.current = { username, isUsernameAvailable };
    }
  });

  return (
    <TextInput
      ref={usernameInputRef}
      onChange={checkUsername}
    />
  );
}

TextInput pseudocode

 TextInput(props, ref) {
    const getTextInput = () => text;

    const [text, setText] = useState(null)
    
    useEffect(() => {
        const { onChange } = props;
        if(text) onChange();   
    }, [text]);  
 
    return <NativeTextInput value={text} onChange={(e) => setText(e.nativeEvent.text)}/>
    ...
 }

Writing fast returns diferent values for the username "tarantino":

tarantino - true | tarantino - false

I think that the problem here is that the state of the UsernameInput "isUsernameAvailable" is updated faster than the TextInput state "text", maybe not the error but I suppose it.

Any ideas how to solve my issue? Thank you.

1 Answers

Have you considered adding a debounce to handle typing really fast and only calling the firebase.isUsernameAvailable endpoint once the user has stopped typing?

const checkUsername = async () => {
    const { firebase } = props;

    // Get the username
    const username = usernameInputRef.current.getText();

    // Check if the username is valid and available on the database
    const isUsernameAvailable = validateUsername(username) && (await firebase.isUsernameAvailable(username));

    setIsUsernameAvailable(isUsernameAvailable);
};

// The debounced function that you call instead of checkUsername
const debouncedCheck = debounce(checkUsername, 500);

I can't see it being good practice to await every single keystroke, not to mention the possibility of running into errors such as the one you are experiencing.

EDIT:

Instead of calling the onChange prop in useEffect try this instead.

TextInput(props, ref) {
    const getTextInput = () => text;

    const [text, setText] = useState(null)

    const handleChange = async (value) => {
      const { onChange } = props;
      setText(value);
      await onChange(value);
    }
 
    return <NativeTextInput value={text} onChangeText={handleChange}/>
    ...
 }

This way, instead of doing this usernameInputRef.current.getText(); in your checkUsername method, you can just use the username value that is being passed in as a parameter.

const checkUsername = async (username) => {
    const { firebase } = props;

    // Check if the username is valid and available on the database
    const isUsernameAvailable = validateUsername(username) && (await firebase.isUsernameAvailable(username));

    setIsUsernameAvailable(isUsernameAvailable);
};
Related