In a high level overview I am building a tinder like app functionality. Where in my example, 4 entries are fetched from my DB, the user is shown one at a time and can click a like or a dislike button. Each button click triggers some asynchronous functions for writing the event to my DB. Once the last entry that was fetched is clicked I need to go and fetch the next 4 entries in my DB.
MY COMPONENT
export const HomePage: React.FC = () => {
const { user } = useUser()
const userId = user?.id
const [current, setCurrent] = useState<number | null>(0)
const [skip, setSkip] = useState(0)
const [url, setUrl] = useState(
`/api/swipes/get-users/?userId=${user?.id}&skip=${skip}`
)
const getUsers = async (url: string) => {
const { data } = await axios.get(url)
return data
}
const { error, data, isLoading, refetch } = useQuery(
['userSwipeData', url],
() => getUsers(url),
{
enabled: !!user?.id,
}
)
const handleRefetchUsers = () => {
setCurrent(0)
setUrl(
`/api/swipes/get-users/?userId=${user?.id}&skip=${
skip FETCH_USERS_PAGINATION_LIMIT
}`
)
refetch()
setSkip(skip FETCH_USERS_PAGINATION_LIMIT)
}
const handleSwipe = async (e: any) => {
if (current === null || !data) return
const { value } = e.target
await handleSwipeType(value, data?.users[current].id)
}
const handleSwipeType = async (type: string, id: string) => {
const values = {
userSwipeOn: id,
currentUser: userId,
}
// if (type === 'YES') {
// if (current && data?.users[current]?.isMatch) {
// await axios.post('/api/swipes/create-match', values)
// alert('You have a match!')
// }
// await axios.post('/api/swipes/like', values)
// } else {
// await axios.post('/api/swipes/dislike', values)
// }
if (current === data.users.length - 1) {
handleRefetchUsers()
} else {
setCurrent(current! 1)
}
}
if (isLoading || current === null) return <Box>Fetching users...</Box>
if (error) return <Box>An error has occurred </Box>
if (data && !data?.users.length) return <Box>No more users for now</Box>
return (
<Box>
<h1>Current User: {data?.users[current].email}</h1>
<Flex>
<button value="NO" onClick={handleSwipe}>
NO
</button>
<button value="YES" onClick={handleSwipe}>
YES
</button>
</Flex>
</Box>
)
}
The problem I am facing is that in this current state when the handleRefetchUsers()
function triggers it works as expected. However, if I am to uncomment all the asynchronous code which I need to run on every click to document the event, once the handleRefetchUsers()
trigger I notice it is skipping 4 entries every time it runs. I'm really at a loss as to why because the check for a final entry should only run after the async code has finished. Any ideas would be helpful.
CodePudding user response:
I'm pretty sure that refetch
doesn't wait for setUrl
to actually update the url
You shouldn't really base one state on another state
To fix it I would replace
const [url, setUrl] = useState(
`/api/swipes/get-users/?userId=${user?.id}&skip=${skip}`
)
with
const url = /api/swipes/get-users/?userId=${user?.id}&skip=${skip}`
and remove refetch
entirely. react-query will refetch anyway because the url changed
CodePudding user response:
There are a few things that could be improved, but your main issue is that setState is async. So when you use setUrl
and then call refetch
, refetch is still looking at the old url
value.
I think a cleaner way would be to use refetch
inside an effect, that has current
and skip
in the dependency array.
Also, url
is a derived state, so it doesn't really need its own state. And you should also be using an arrow function when a new state relies on the previous state - again because setState is async, and it's possible that you are referencing an old state.
const buildUrl = (user, skip) => user?.id ? `/api/swipes/get-users/?userId=${user?.id}&skip=${skip}` : ''
export const HomePage: React.FC = () => {
const { user } = useUser()
const userId = user?.id
const [current, setCurrent] = useState<number>(0) // current page
const [skip, setSkip] = useState(0)
const getUsers = async (url: string) => {
const { data } = await axios.get(url)
return data
}
const { error, data, isLoading, refetch } = useQuery(
['userSwipeData', buildUrl(user, skip)],
() => getUsers(buildUrl(user, skip)),
{
enabled: !!user?.id,
}
)
const handleRefetchUsers = () => {
setCurrent(0)
setSkip((prev) => prev FETCH_USERS_PAGINATION_LIMIT)
}
const handleSwipe = async (e: any) => {
if (current === null || !data) return
const { value } = e.target
await handleSwipeType(value, data?.users[current].id)
}
const handleSwipeType = async (type: string, id: string) => {
const values = {
userSwipeOn: id,
currentUser: userId,
}
if (type === 'YES') {
if (current && data?.users[current]?.isMatch) {
await axios.post('/api/swipes/create-match', values)
alert('You have a match!')
}
await axios.post('/api/swipes/like', values)
} else {
await axios.post('/api/swipes/dislike', values)
}
if (current === data.users.length - 1) {
handleRefetchUsers()
} else {
setCurrent((prev) => prev 1)
}
}
useEffect(() => {
refetch()
}, [current, skip])
if (isLoading || current === null) return <Box>Fetching users...</Box>
if (error) return <Box>An error has occurred </Box>
if (data && !data?.users.length) return <Box>No more users for now</Box>
return (
<Box>
<h1>Current User: {data?.users[current].email}</h1>
<Flex>
<button value="NO" onClick={handleSwipe}>
NO
</button>
<button value="YES" onClick={handleSwipe}>
YES
</button>
</Flex>
</Box>
)
}