skip to Main Content

I allow users to search for other users within my component using a query state variable to track this. Whenever this query changes, after a timeout, the query is performed and the new users are mapped on screen. Here is the code for this:

const [query, setQuery] = useState("")
const [users, setUsers] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [noResultsFound, setNoResultsFound] = useState(false)


useEffect(() => {
    if (query === "" || !query) return

    let timer = setTimeout(async () => {
        setIsLoading(true)
        const res = await axiosPrivate.get(`${BASE_URL}/api/users/get_users/${query}`, { headers: { "x-auth-token": token } })
        if (res?.data?.length === 0) {
            setUsers([])
            setNoResultsFound(true)
        } else {
            setUsers(res.data)
            setNoResultsFound(false)
            console.log(users)
        }

        setIsLoading(false)
    }, 500)

    return () => {
        clearTimeout(timer)
    }
}, [query])

Then I map the users array on the screen if they are available, otherwise I display a loading indicator. If no results were found for the query, then some text indicating this is displayed instead.

            {isLoading && (
                <Spinner
                    noDelay={true}
                    withStyles={false}
                />
            )}

            {!isLoading && noResultsFound === false && users.length > 0 && (
                <>
                    {users.map((user) => (
                        <p key={uuidv4()}{ user.username}</p>
                    ))}
                </>
            )}

            {!isLoading && noResultsFound === true && users.length === 0 && <p>No users</p>}

The problem I have is that if the query changes, and the exact same results are returned from the API, a flicker occurs for each of the users list items. I have ensured all my keys for the mapped items are unique. How can I ensure that if the exact same results are returned from the API, then don’t re-render the list items to avoid the flicker on screen. If this is not a solution, is there any potential methods I could use in order to avoid this flickering? Thanks.

3

Answers


  1.             {!isLoading && noResultsFound === false && users.length > 0 && (
                    <>
                        {users.map((user) => (
                            <p key={uuidv4()}>{user.username}</p>
                        ))}
                    </>
                )}
    

    You only render the list of users if isLoading is false. So that means that every time you kick off an API request, the list is hidden until the API request completes. You can just remove !isLoading from this conditional, but I suspect you might run into other UX issues if you do that.

    I know you shouldn’t add libraries for such a simple thing, but I suggest to use tanstack/query or useSWR – it drastically simplifies this type of code and the solutions to all your UX problems are answered somewhere in their documentation.

    PS: Your keys make your list less performant – they change every time the list is rendered – you should use something like this

                        {users.map((user) => (
                            <p key={user.id}>{user.username}</p>
                        ))}
    
    Login or Signup to reply.
  2. The problem is that you are generating a new key on the fly for every render.

    <p key={uuidv4()}>{ user.username}</p>
    

    You need to understand how keys work under the hood in React. React uses the key prop to uniquely identify the component. When the key changes, React will assume that it is a new component.

    In this case, React will encounter a paragraph tag with a new key on every render. This will make the virtual DOM render a new component.

    It is a bad practice to generate a key in this manner. Not providing a key is better than providing a key that is not in the control of the user.

    Solution

    Every user will have a unique property associated with it. Ideally it would be user.id. In this case, the username will be unique as well. This will server as a better candidate for a key.

    <p key={user.username}>{ user.username}</p>
    

    Now, React can re-use the previously rendered components by matching the keys.

    Login or Signup to reply.
  3. import { useMemo } from 'react';
    
    // ...
    
    const MemoizedUsers = useMemo(() => users, [users]);
    
    return (
      <>
        {isLoading && (
          <Spinner
            noDelay={true}
            withStyles={false}
          />
        )}
    
        {!isLoading && noResultsFound === false && MemoizedUsers.length > 0 && (
          <>
            {MemoizedUsers.map((user) => (
              <p key={uuidv4()}>{user.username}</p>
            ))}
          </>
        )}
    
        {!isLoading && noResultsFound === true && MemoizedUsers.length === 0 && <p>No users</p>}
      </>
    );

    In this modified code, the MemoizedUsers variable is a memoized version of the users array. It will only change if the reference of the users array changes. This way, when the exact same results are returned from the API, and the users array reference remains the same, it won’t trigger unnecessary re-renders and flickering.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search