-
Notifications
You must be signed in to change notification settings - Fork 3
feat(use-presence): allow use-presence to be driven by arbitrary data #11
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
Changes from 8 commits
fcfe09e
b59bb7e
656a566
09165db
895ea66
bc5baaf
f76998d
51e1ed7
c17b024
6cef701
e558eef
ef17511
56bdd1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import usePresence from './use-presence'; | ||
|
||
export * from './use-presence-switch'; | ||
|
||
export default usePresence; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import {useState, useEffect} from 'react'; | ||
import usePresence from './use-presence'; | ||
|
||
export function usePresenceSwitch<ItemType>( | ||
latestItem: ItemType, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
opts: Parameters<typeof usePresence>[1] | ||
) { | ||
const [item, setItem] = useState(latestItem); | ||
const [shouldBeMounted, setShouldBeMounted] = useState(!isNil(item)); | ||
const {isMounted, ...otherStates} = usePresence(shouldBeMounted, opts); | ||
useEffect(() => { | ||
if (item !== latestItem) { | ||
if (isMounted) { | ||
setShouldBeMounted(false); | ||
} else if (!isNil(latestItem)) { | ||
setItem(latestItem); | ||
setShouldBeMounted(true); | ||
} | ||
} else if (isNil(latestItem)) { | ||
setShouldBeMounted(false); | ||
} else if (!isNil(latestItem)) { | ||
setShouldBeMounted(true); | ||
} | ||
}, [latestItem, item, shouldBeMounted, isMounted]); | ||
|
||
return { | ||
...otherStates, | ||
isMounted: isMounted && !isNil(item), | ||
item | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we call this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}; | ||
} | ||
|
||
function isNil<ItemType>(value: ItemType) { | ||
return value === null || value === undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A shortcut for this is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally have not used |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import {render, waitFor} from '@testing-library/react'; | ||
import * as React from 'react'; | ||
import {usePresenceSwitch} from '../src'; | ||
|
||
function Expander({ | ||
initialEnter, | ||
latestItem, | ||
transitionDuration = 50 | ||
}: { | ||
initialEnter?: boolean; | ||
latestItem: {text: string}; | ||
transitionDuration?: number; | ||
}) { | ||
const {item, ...values} = usePresenceSwitch(latestItem, { | ||
transitionDuration, | ||
initialEnter | ||
}); | ||
const {isAnimating, isMounted, isVisible} = values; | ||
const testId = | ||
Object.entries(values) | ||
.filter(([, value]) => value) | ||
.map(([key]) => key) | ||
.join(', ') || 'none'; | ||
|
||
return ( | ||
<div data-testid={testId}> | ||
{isMounted && ( | ||
<div | ||
style={{ | ||
overflow: 'hidden', | ||
maxHeight: 0, | ||
transitionDuration: `${transitionDuration}ms`, | ||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', | ||
transitionProperty: 'max-height, opacity', | ||
opacity: 0, | ||
...(isVisible && { | ||
maxHeight: 9999, | ||
opacity: 1, | ||
transitionTimingFunction: 'cubic-bezier(0.8, 0, 0.6, 1)' | ||
}), | ||
...(isAnimating && {willChange: 'max-height, opacity'}) | ||
}} | ||
> | ||
{item.text} | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
it("can animate the exit and re-entrance of a component that has changed it's rendered data", async () => { | ||
const {getByTestId, getByText, rerender} = render( | ||
<Expander latestItem={{text: 'initial value'}} /> | ||
); | ||
getByTestId('isVisible, isMounted'); | ||
getByText('initial value'); | ||
rerender(<Expander latestItem={{text: 're-assigned value'}} />); | ||
getByTestId('isAnimating, isExiting, isMounted'); | ||
getByText('initial value'); | ||
await waitFor(() => getByTestId('isVisible, isMounted')); | ||
getByText('re-assigned value'); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool! 🙌 If you feel like it we could test some more states like unmounting completely (like your initial demo allows it), but up to you! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! 👍 |
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 guess we could just use default exports for both files to be consistent, right?
There's btw. a one-liner for reexporting:
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.
Will do 👍