Lesson 9 - reusability
February 15, 2018 · View on GitHub
The MenuItem component is already highly re-usable, but we've still got some
specific data stuck inside the Menu component. In particular: the list of menu
items.
Challenge 14
Extract the items array out of the Menu component, and instead, pass it in
as a prop.
Also, pass in the initially active menu index as a prop activeMenu. You'll
need to update the getInitialState method to reference
this.props.activeMenu.
Menu is starting to look pretty re-usable now; We can give it any array of
things and it'll render them, as well as passing down which is the currently
active element.
If we think about it some more, the Menu component really is only interested
in displaying a list, and tracking the currently active one. So, it's not really
a Menu, but probably more likely a SelectableGroup.
Challenge 15
Rename the menu.js file to selectable-group.js, and update the require
call in src/js/index.js.
Also rename Menu to SelectableGroup to avoid confusion in src/js/index.js.
How can we extract out the dependency of MenuItem contained within
SelectableGroup?
React provides a special prop to every component that has any nested children:
this.props.children. For example, in this case:
<ul>
<li>Hi</li>
<li>Ho</li>
</ul>
The this.props.children of the ul component would be:
[
<li>Hi</li>,
<li>Ho</li>
]
This allows us to pass the MenuItem components as the children prop to our
SelectableGroup component:
<SelectableGroup>
<MenuItem name={'Hi'} />
<MenuItem name={'Ho'} />
</SelectableGroup>
Challenge 16
Move the .map conversion of array -> MenuItem's out of
src/js/selectable-group.js and into src/js/index.js. Then render the
menuItems array as the children of <SelectableGroup>.
Notes
- You'll need to add
{this.props.children}to theSelectableGroup'srendermethod. This will loop over the childen you've passed in and actually render them. Without this, nothing will be rendered. - It may be easier to comment out the logic for
isActivewithinSelectableGroupuntil the next challenge to get this one running.
But we've lost our isActive prop again! Once again React comes to the rescue,
allowing us to set the props from within the SelectableGroup component with
the
React.cloneElement
method:
ReactElement cloneElement( ReactElement element, [object props], [children ...] )Clone and return a new ReactElement using element as the starting point. The resulting element will have the original element's props with the new props merged in shallowly.
It is used like this:
let newChildren = React.Children.map(this.props.children, function(child, index) {
return React.cloneElement(child, { foo: true })
})
Where foo: true is setting a new prop on the child.
Challenge 17
Use React.cloneElement within SelectableGroup to set isActive on the
correct element.
Notes:
- Compare the
activeMenustate value against theindexto see if it's the active one.
Now SelectableGroup is 100% generic, passing through an isActive prop, and
allowing rendering of arbitrary children which could be absolutely any type of
component.
Finally, we want to be able to change which is the currently active item.
To do this, we can pass an extra prop to the children in React.cloneElement.
This prop should probably be called onActivate, and is a function which sets
the state of activeMenu.
If we wanted to activate menu items on click, then we can do so within
MenuItem by triggering the this.props.onActivate method within a click
handler.
Challenge 18
Similarly to the hover event
handler,
add a click event handler. This handler should execute this.props.onActivate,
and pass in this.props.key as its only argument.
Notes:
- The
onActivatemethod's first parameter is the key of the currently active menu, so you can set that to theactiveMenustate withinSelectableGroup
Success! We have now built up a really generic component (SelectableGroup)
which renders children for us and updates its state to track the currently
selected child. We also have a slightly more specific MenuItem component which
can react to hovers and clicks!
In this way, it's possible to build up a library of very powerful, but still quite lean and well encapsulated components.