-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(closes #527): Enable cow renaming #528
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@@ -66,7 +66,7 @@ describe('Inventory Component', () => { | |||
const searchInput = screen.getByPlaceholderText('Search inventory...') | |||
fireEvent.change(searchInput, { target: { value: 'Carrot' } }) | |||
|
|||
vitest.runAllTimers() | |||
vitest.advanceTimersByTime(1000) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This improves a flakey test introduced by #526.
@@ -11,7 +11,7 @@ export const useMountState = () => { | |||
} | |||
}, [isMountedRef]) | |||
|
|||
const isMounted = () => isMountedRef.current | |||
const isMounted = useCallback(() => isMountedRef.current, [isMountedRef]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes the root cause of #527.
Specifically, the fact that this wasn't wrapped in a useCallback
meant that this useEffect
would run on every render and reset the cow name (see line 137):
farmhand/src/components/CowCard/CowCard.js
Lines 128 to 138 in 56f72dc
useEffect(() => { | |
;(async () => { | |
const cowImage = await getCowImage(cow) | |
if (isMounted() === false) return | |
setCowImage(cowImage) | |
})() | |
setDisplayName(getCowDisplayName(cow, id, allowCustomPeerCowNames)) | |
}, [cow, id, allowCustomPeerCowNames, isMounted]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using a ref in this code is kind of strange to me. if you like the ref use this isn't a big deal but i would have written this hook like:
const [isMounted, setIsMounted] = useState(false)
useEffect(() => setIsMounted(true), [])
return { isMounted }
in general though having isMounted checks is a code smell for me so it may be more
worthwhile to try to refactor all the places this hook is needed than to worry about rewriting it right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad you raised this because it made me realize that #526 introduced the inclusion of https://usehooks-ts.com/. This library provides an implementation of useIsMounted
which effectively abstracts away the contents of this hook. Ironically the library implementation seems equivalent to what we have here, but since it's in a library we don't have to worry too much about the implementation details so long as it works (which it does).
I've switched to using the usehooks-ts
implementation in 72d110b to eliminate the maintenance burden of our custom one.
in general though having isMounted checks is a code smell for me so it may be more
worthwhile to try to refactor all the places this hook is needed than to worry about rewriting it right now.
I generally agree and try to avoid using isMounted
checks for this reason. In this case it was necessary for Cow image generation:
farmhand/src/components/CowCard/CowCard.js
Lines 129 to 135 in 72d110b
;(async () => { | |
const cowImage = await getCowImage(cow) | |
if (isMounted() === false) return | |
setCowImage(cowImage) | |
})() |
In this part of the code, the Cow image is generated dynamically as an asynchronous operation. By the time the image generation is complete, it's possible that the component has unmounted which would mean setCowImage(cowImage)
would be improperly setting state on an unmounted component. There are probably cleaner abstractions for this sort of thing, but this seemed like the simplest solution. I don't know that we'd want to repeat this pattern too much, but I think this is the only part of the code base that we do this right now so I think that this bit of isolated awkwardness is acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh that's great we have a library version of this to use instead! also if it's only used for that one use case that's not a big deal at all. i did not look around for the uses
@@ -46,7 +46,7 @@ | |||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ | |||
|
|||
/* Module Resolution Options */ | |||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ | |||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to make type checking work better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leaving some thoughts but i'm also ok with a merge to get the bug fixed. i don't need to hold that up over semantics 😄
userEvent.clear(nameInput) | ||
userEvent.type(nameInput, customName) | ||
|
||
await waitFor(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think you should be able to await the userEvent.type calls and avoid waitFor entirely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think you should be able to await the userEvent.type calls and avoid waitFor entirely
It turns out that clear
and type
only return Promises in @testing-library/react
v14. Farmhand currently uses v13, and upgrading to v14 breaks a number of other tests so it's not worth making that change in a narrowly-scope bugfix PR such as this.
However, it appears that the await waitFor(...)
isn't even necessary here (I think it was for an earlier iteration), so I've removed it in 9601f41. Thanks for bringing this up helping to simplify this part of the code!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah right, i forgot the switch to async was in later versions
@@ -11,7 +11,7 @@ export const useMountState = () => { | |||
} | |||
}, [isMountedRef]) | |||
|
|||
const isMounted = () => isMountedRef.current | |||
const isMounted = useCallback(() => isMountedRef.current, [isMountedRef]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using a ref in this code is kind of strange to me. if you like the ref use this isn't a big deal but i would have written this hook like:
const [isMounted, setIsMounted] = useState(false)
useEffect(() => setIsMounted(true), [])
return { isMounted }
in general though having isMounted checks is a code smell for me so it may be more
worthwhile to try to refactor all the places this hook is needed than to worry about rewriting it right now.
56f72dc
to
9601f41
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review @lstebner! The code is looking better with your feedback incorporated, as always. 🙂
I'll merge this now!
@@ -11,7 +11,7 @@ export const useMountState = () => { | |||
} | |||
}, [isMountedRef]) | |||
|
|||
const isMounted = () => isMountedRef.current | |||
const isMounted = useCallback(() => isMountedRef.current, [isMountedRef]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad you raised this because it made me realize that #526 introduced the inclusion of https://usehooks-ts.com/. This library provides an implementation of useIsMounted
which effectively abstracts away the contents of this hook. Ironically the library implementation seems equivalent to what we have here, but since it's in a library we don't have to worry too much about the implementation details so long as it works (which it does).
I've switched to using the usehooks-ts
implementation in 72d110b to eliminate the maintenance burden of our custom one.
in general though having isMounted checks is a code smell for me so it may be more
worthwhile to try to refactor all the places this hook is needed than to worry about rewriting it right now.
I generally agree and try to avoid using isMounted
checks for this reason. In this case it was necessary for Cow image generation:
farmhand/src/components/CowCard/CowCard.js
Lines 129 to 135 in 72d110b
;(async () => { | |
const cowImage = await getCowImage(cow) | |
if (isMounted() === false) return | |
setCowImage(cowImage) | |
})() |
In this part of the code, the Cow image is generated dynamically as an asynchronous operation. By the time the image generation is complete, it's possible that the component has unmounted which would mean setCowImage(cowImage)
would be improperly setting state on an unmounted component. There are probably cleaner abstractions for this sort of thing, but this seemed like the simplest solution. I don't know that we'd want to repeat this pattern too much, but I think this is the only part of the code base that we do this right now so I think that this bit of isolated awkwardness is acceptable.
userEvent.clear(nameInput) | ||
userEvent.type(nameInput, customName) | ||
|
||
await waitFor(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think you should be able to await the userEvent.type calls and avoid waitFor entirely
It turns out that clear
and type
only return Promises in @testing-library/react
v14. Farmhand currently uses v13, and upgrading to v14 breaks a number of other tests so it's not worth making that change in a narrowly-scope bugfix PR such as this.
However, it appears that the await waitFor(...)
isn't even necessary here (I think it was for an earlier iteration), so I've removed it in 9601f41. Thanks for bringing this up helping to simplify this part of the code!
@@ -123,7 +123,7 @@ export const CowCard = ( | |||
isCowOfferedForTradeByPeer && cowIdOfferedForTrade.length > 0 | |||
) | |||
|
|||
const { isMounted } = useMountState() | |||
const isMounted = useIsMounted() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What this PR does
This PR fixes #527, a regression that prevented players from giving their cows custom names.
How this change can be validated
Successfully rename a cow (this is only possible with cows that were not received via trade).
Questions or concerns about this change
I have no idea how long this was broken for. 😭