-
Notifications
You must be signed in to change notification settings - Fork 3
Add recommendations tracking #107
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
Add recommendations tracking #107
Conversation
mocca102
commented
Nov 22, 2023
- Track recommendation click
- Track recommendation view
9149817 to
e6e2b51
Compare
esezen
left a comment
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 is looking great! Thanks for making the changes.
I left some small comments, let me know what you think
Lets also get a review from the Professor @deomsj
deomsj
left a comment
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.
nice work on this @mocca102 and @esezen!!!
I've left a number of ideas, questions, and suggestions as inline comments in this review. Looking forward to hearing your thoughts on these points and having another look at this together on a pairing call or async for another round of review.
I appreciate all of your great work on this & I'll follow your lead on how I can best support with next steps :)
| .map((section) => section.ref); | ||
|
|
||
| useEffect(() => { | ||
| const observer = new IntersectionObserver( |
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 confused. What do we mean by "intersection" here?
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.
whoa... https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
this is very coooool :)
| import ConstructorIO from '@constructor-io/constructorio-client-javascript'; | ||
| import { Section } from '../types'; | ||
|
|
||
| function useRecommendationsObserver( |
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.
can we add some brief comment above this function definition describing the scope of responsibility for this hook? We are using some very cool patterns in here that will not be obvious to many developers. I'd love to see explicit documentation for what this hook is doing for us and what needs to be configured elsewhere in the lib code or in a lib consumer's src code in order for this to behave as it is intended
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.
Let me know what you think 55c168e#diff-54d8f2b4755e1b9a3357013b05b24a47635a9fbae6dc1ab0116aa0dcaf69aa05R7
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.
YES. Well done sir. I found these explanations to be very, very helpful :)
src/hooks/useSections.ts
Outdated
| useEffect(() => { | ||
| setActiveSections(zeroStateActiveSections ? zeroStateSections : sections); | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [zeroStateActiveSections]); |
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 may be missing something, but this looks like derived state at this point.
Can we simplify?
all of this:
// Define All Sections
const [activeSections, setActiveSections] = useState<UserDefinedSection[]>(
zeroStateActiveSections ? zeroStateSections : sections
);
useEffect(() => {
setActiveSections(zeroStateActiveSections ? zeroStateSections : sections);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [zeroStateActiveSections]);to just, this:
this:
const activeSections = zeroStateActiveSections ? zeroStateSections : sections;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 thought I wanted to make it reactive to only zeroStateActiveSections so it updates the state only if zeroStateActiveSections changes. But whenever zeroStateActiveSections, zeroStateSections and sections also change and they're passed as props to the hook so the whole hook rerun every time any way.
TLDR; I thought this was an optimization but I don't think it is anymore
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.
src/hooks/useSections.ts
Outdated
| ); | ||
| }, [autocompleteResults, recommendationsResults, activeSections]); | ||
|
|
||
| // Reset sections |
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.
why do we need this effect? I'm wondering why we need to manage activeSections as its own state here at all instead of deriving it from other state passed in to this hook...
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.
Removed
src/utils.ts
Outdated
|
|
||
| if (sectionConfig.type === 'recommendations') { | ||
| // If user provided a ref in config, use it. Otherwise, use the ref from our refs array | ||
| section.ref = sectionConfig.ref || sectionsRefs.current[index]; |
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.
- when would this value arrive to this point via
sectionConfig.ref? - when would this value arrive to this point via
sectionsRefs.current[index]
can we add comments or use some precisely named consts here to make this explicit to anyone referencing or building on top of this 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.
Let me know if this clears things up 55c168e#diff-39b2554fd18da165b59a6351b1aafff3714e2a80c1435f2de9706355b4d32351R173
deomsj
left a comment
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.
Nicely done @mocca102!
I haven't tried breaking functionality locally yet, but I'm lovin the changes made here from a code clarity / maintainability perspective.
I left a couple more questions & ideas. Holla for pairing sesh or for another review as you like :)
| * Custom hook that observes the visibility of recommendation sections and calls trackRecommendationView event. | ||
| * This is done by using the IntersectionObserver API to observe the visibility of each recommendation section. | ||
| * That is done by passing the ref of each recommendation section to the IntersectionObserver. | ||
| * The refs are either passed by the user in section config or generated by the library by default. |
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.
vocab alignment:
- "user" - end user who's browser this production code is rendered on
- "lib consumer" - developer / team who is consuming this library and building on top of it
src/utils.ts
Outdated
| data: sectionData, | ||
| }; | ||
|
|
||
| // If user provided a ref in `sectionConfig`, use it. Otherwise, use the ref from our library generated refs array |
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.
Four questions:
- how will the lib consumer know that they can pass these refs?
- how will lib consumer know how to pass this ref?
- what happens if they don't pass this ref and are using the lib's component interface?
- what happens if they don't pass this ref and are using the lib's hook interface?
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.
- It's a prop on type
SectionConfigurationhttps://github.com/Constructor-io/constructorio-ui-autocomplete/pull/107/files/55c168e54f3bc1568827cba615592e193f2fc3c1#diff-c54113cf61ec99691748a3890bfbeb00e10efb3f0a76f03a0fd9ec49072e410aR83 - same as 1 but we can add more documentation explaining that
- and 4. There's no change between hooks and component interface as long as they use getSectionProps
We need to add docs for getSectionProps
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 is a great callout. This would have made things more clear if added from the beginning. Added here
06ced4f#diff-8fa4b52909f895e8cda060d2035234e0a42ca2c7d3f8f8de1b35a056537bf199R40 @deomsj
deomsj
left a comment
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.
Findings from dev build on my machine:
- all storybook tests are passing!
- all rec view and rec click events look great! (screenshot below for more detail)
- tried a handfull of stories without recs and both UX and network traffic continue to behave as expected!
Excellent work on this, @mocca102!!!