Skip to content

Instantly share code, notes, and snippets.

@tonymamo
Last active February 24, 2022 19:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tonymamo/1516903eee8f8c967e6504d153c3bda3 to your computer and use it in GitHub Desktop.
Save tonymamo/1516903eee8f8c967e6504d153c3bda3 to your computer and use it in GitHub Desktop.
Technical exercise
/*
Prompt:
We have defined a basic dropdown via the Dropdown and DropdownItem components below, with example usage
in the ExampleNav component. The Dropdown and DropdownItem components have some problems, and also
have room for improvements (doesn't everything?) A couple items TODO here (make sure to explain with comments!)
0. How are you today? 😊
- doing great!
1. Please fix any obvious issues you see with the dropdown and then save your gist.
- toggle() wasn't actually changing the state ever
- made DropdownItem return an <li> with a link, since Dropdown has a <ul> around its children
- I haven't worked with class components in a long time now, but I think the onClick calling of
the `toggle` function in the button in Dropdown component needs to either be bound to `this`
in the constructor, or can be called with an arrow function like I added
- add a role and aria-label to the <nav> element. not necessarily needed as <nav> carries
semantic meaning, but doesnt hurt
2. Please then make improvements to the dropdown dnd then save your gist again.
- First thing I would do is change these class components to function components, and use hooks
for setting state, but thats probably outside the scope of this exercise 😀
- I would also use typescript for type checking and set up eslint and prettier to ensure
consistency in formatting and catch any issues
- I did run prettier on this just to make things a little more readable for me and not go longer
than 100 characters wide in the code
- Instead of classnames, I would use styled-components to handle dynamic styling that can be
interpolated based on props
3. Consider the different ways that this dropdown might be used and what changes would
be neccessary to make it more flexible.
- the dropdown items might not always be links, they could be something that looks like a link
but needs an onClick instead
- sometimes there may be icons before or after the text in the dropdown items
- different styling options depending on where its used
4. If we wanted to sync the dropdown selection to a server with this hypothetial "syncing library"
`app.sync('PATCH', 'users/'+app.USER.id, { dropdown_1_state: {true,false} })` where would this
be included? Should the state be read again from the server to show the dropdown open/closed
on page load?
- I would probably include the api call where each <Dropdown> is used in the code, so in the
ExampleNav in this case, and then pass a prop to <Dropdown> that would then initialize its
state to the server state. Then, also pass a function that would update the server that would
be called in the `toggle()` function
5. If we wanted to pass children (like this example) OR a Promise that resolves to an array of items
what changes should be made? (just a sentence or two or some code is ok).
- Instead of children, you could pass down a prop to Dropdown that was an array of objects that
had the necessary pieces of info in it, like the href and link text, and then simply map over
those array items in the Dropdown component to render them. If waiting for a Promise to resolve
with those items, you could have a loading state that looks at the length of the array of items
or simple return null until the data is there.
PS: No need to worry about CSS or about making it actually run.
*/
import React, { useEffect, useState } from 'react'
import styled from 'styled-components'
const StyledDropdown = styled.div`
position: relative;
/* rest of css for dropdown wrapper would go here */
`
const StyledDropdownListWrapper = styled.ul`
/* remove ul default styling and add whatever else needed for design */
/* also add position: absolute; top/left/right etc */
`
const StyledDropdownItem = styled.button`
/* share common styling between <button> and <a> */
`
type DropdownItemType = {
href?: string
onClick?: () => void
text: string
icon?: string // could also be a React node, image, or something else, depending on how icons are done
}
type DropdownProps = {
label: string
items: DropdownItemType[]
onUpdate: (location: string) => void
}
const Dropdown: React.FC<DropdownProps> = (props): JSX.Element => {
useEffect(() => {
// api call to get the initial state for each dropdown on mount, only ran once (empty dep array)
// and then on it to update the state
// getInitialState().then((res) => res.json()).then((state) => setIsOpen(state))
}, [])
const [isOpen, setIsOpen] = useState(false)
return (
<StyledDropdown>
{/* would normally have an atomic button component called Button here, so no need for id, classname, etc. */}
<button
type="button"
aria-haspopup="true"
// typo on expanded (was expended)
aria-expanded={isOpen}
onClick={() => {
// use label as our unique identifer for each dropdown, and update the server state
// with the function passed down
props.onUpdate(props.label);
setIsOpen(!isOpen);
}}
>
{props.label}
</button>
{/* instead of using css to show/hide, we can just use react/javascript conditional to render the list or not */}
{isOpen && (
<StyledDropdownListWrapper aria-labelledby="dropdownButton" role="menu">
{props.items.map((item) =>
// if the item has an href, use the `as` prop to convert from a <button> to an <a>
item.href ? (
<StyledDropdownItem as="a" href={item.href} key={item.text}>
{item.icon || null} {item.text}
</StyledDropdownItem>
) : (
// otherwise, it will render a <button>
<StyledDropdownItem onClick={item.onClick} key={item.text}>
{item.icon || null} {item.text}
</StyledDropdownItem>
),
)}
</StyledDropdownListWrapper>
)}
</StyledDropdown>
)
}
type ExampleNavProps = unknown
const ExampleNav = (props: ExampleNavProps): JSX.Element => {
const onUpdate = (location: string) => {
// a function that we pass down to the dropdowns themeselves, and then it gets called from there
// app.sync('PATCH', 'users/' + app.USER.id, { location, { true, false} })
}
return (
<nav role="navigation" aria-label="Main Menu">
<a href="/page1">Page 1</a>
<Dropdown
label="More items"
onUpdate={onUpdate}
items={[
{
href: '/page2',
text: 'Page 2',
},
{
href: '/page3',
text: 'Page 3',
},
// ...
]}
/>
<Dropdown
label="Even more items"
onUpdate={onUpdate}
// items for second dropdown go here, could be inline or imported from elsewhere,
// like a utility file where all navigation and routing is configured for example
items={[]}
/>
</nav>
)
}
// These would be all separate files normally, but combining them here to keep it just one file
export { Dropdown, ExampleNav }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment