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
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
oruseSWR
– 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
The problem is that you are generating a new key on the fly for every render.
You need to understand how
key
s work under the hood in React. React uses thekey
prop to uniquely identify the component. When thekey
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 akey
is better than providing akey
that is not in the control of the user.Solution
Every
user
will have a unique property associated with it. Ideally it would beuser.id
. In this case, the username will be unique as well. This will server as a better candidate for akey
.Now, React can re-use the previously rendered components by matching the keys.
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.