Last active
February 24, 2022 19:01
-
-
Save tonymamo/1516903eee8f8c967e6504d153c3bda3 to your computer and use it in GitHub Desktop.
Technical exercise
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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