README
VCNKit/Essentials
@vcnkit/essentials
contains essential utilities, decorators and theming for VCNKit.
Installation
NPM
$ npm install --save @vcnkit/essentials
Yarn
$ yarn add @vcnkit/essentials
Utilities
noop()
Simple void function to use as a default prop when your component expects a function.
import { noop } from '@vcnkit/essentials';
SomeComponent.defaultProps = {
onClick: noop,
};
omit(props, [ prop, ... ])
Omits the specified props from the given prop object
import { omit } from '@vcnkit/essentials';
const SomeComponent = (props) => (
<div { ...omit(props, [ 'someProp', 'anotherProp' ]) } >
'someProp' and 'anotherProp' have been omitted.
</div>
)
extract(props, [ prop, ... ])
Extracts the specified props from the given prop object, basically the reverse of omit
import { extract } from '@vcnkit/essentials';
const SomeComponent = (props) => (
<div { ...extract(props, [ 'role', 'id' ]) }>
'role' and 'id' will have been attached to this div.
</div>
);
getRelatedTarget(event)
Internet Explorer compatible relatedTarget for blur
events. This function will give you the node that will receive focus after the blur has occured.
import { getRelatedTarget } from '@vcnkit/essentials';
class SomeComponent extends React.Component {
handleBlur = event => {
// Will contain the Node that received focus.
const relatedTarget = getRelatedTarget(event);
};
render() {
return (
<input onBlur={ this.handleBlur } />
);
}
}
isTargetDescendantOf(root, target)
Checks if the given target
Node is a descendant of root
(but not the same node).
import { isTargetDescendantOf } from '@vcnkit/essentials';
function isBodyDescendantOfDocument() {
return isTargetDescendantOf(document, document.body);
}
createHoC(Component, WrappedComponent)
Utility to reduce boilerplate when creating Higher-Order Components
It will provide the resulting HoC with a displayName that is a cobmination of Component
and WrappedComponent
. It will also take care of forwarding refs and hoisting the non-react statics.
import { createHoC } from '@vcnkit/essentials';
function myHoC(WrappedComponent) {
class MyHoC extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return (
<WrappedComponent
ref={ forwardRef }
{ ...rest }
/>
)
}
}
MyHoC.displayName = 'MyHoC';
return createHoC(MyHoC, WrappedComponent);
}
Decorators
uniqueId
Passes a getId()
function through props to the decorated component. This function will always return the same identifier for the same instance.
Optionally the getId()
function takes a suffix
-parameter, this will result in the given suffix being added to the identifier.
import { uniqueId } from '@vcnkit/essentials';
@uniqueId()
class SomeComponent extends React.Component {
render() {
const { getId } = this.props;
return (
<div>
<label htmlFor={ getId('input') }>Label</label>
<input
id={ getId('input') }
type="text"
/>
</div>
);
}
}
Stateless components can use the decorator by wrappign itself.
import { uniqueId } from '@vcnkit/essentials';
const SomeComponent = ({ getId }) => (
<div>
<label htmlFor={ getId('input') }>Label</label>
<input
id={ getId('input') }
type="text"
/>
</div>
);
export default uniqueId()(SomeComponent);
hoverable
Provides decorated components with a hovering
-prop. This props tells the component if the user is currently hovering over the element.
To make this work, this decorator provides the onMouseEnter
and onMouseLeave
props which should be attached to your component.
import { hoverable } from '@vcnkit/essentials';
@hoverable()
class SomeComponent extends React.Component {
render() {
const { hovering, onMouseEnter, onMouseLeave, ...rest } = this.props;
return (
<div
onMouseEnter={ onMouseEnter }
onMouseLeave={ onMouseLeave }
{ ...rest }
>
{ hovering ? 'User is hovering over this div!' : 'User is not hovering over this div' }
</div>
);
}
}
Stateless components can use the decorator by wrapping itself.
import { hoverable } from '@vcnkit/essentials';
const SomeComponent = ({ hovering, onMouseEnter, onMouseLeave, ...rest ) => (
<div
onMouseEnter={ onMouseEnter }
onMouseLeave={ onMouseLeave }
{ ...rest }
>
{ hovering ? 'User is hovering over this element!' : 'User is not hovering over this element' }
</div>
);
export default hoverable()(SomeComponent);
focusable(focusOnChildFocus = false)
Provides decorated components with a focused
-prop. This prop tells the component if the component currently has focus, or if focusOnChildFocus
is true if a child component has focus.
This decorator will not provide a tabIndex
prop, the component itself is responsible for giving itself or it's children a tabIndex
prop.
import { focusable } from '@vcnkit/essentials';
@focusable()
class SomeComponent extends React.Component {
render() {
const { focused, onFocus, onBlur, ...rest } = this.props;
return (
<div
onFocus={ onFocus }
onBlur={ onBlur }
tabIndex={ 0 }
{ ...rest }
>
{ focused ? 'Element has focus' : 'Element does not have focus' }
</div>
);
}
}
Stateless components can use the decorator by wrapping itself.
import { focusable } from '@vcnkit/essentials';
const SomeComponent = ({ hovering, onMouseEnter, onMouseOut, ...rest ) => (
<div
onFocus={ onFocus }
onBlur={ onBlur }
tabIndex={ 0 }
{ ...rest }
>
{ focused ? 'Element has focus' : 'Element does not have focus' }
</div>
);
export default focusable()(SomeComponent);
expandable
Provides decorated components with a expanded
-prop. The expansion state can either be controlled from another component by passing your own expanded
-prop to the decorated component. When it changes, an attached onChange(currentExpandedState: bool)
will be triggered.
It is also possible to let the decorator keep the state internal, you can set the default state by passing initialExpanded
.
import { expandable } from '@vcnkit/essentials';
@expandable()
class SomeComponent extends React.Component {
render() {
const { expanded, onChange } = this.props;
return (
<div>
<h1 onClick={ onChange }>Click here to toggle</h1>
{ expanded && (
<p>Expanded!</p>
) }
</div>
)
}
}
// Using internal state (starts expanded)
const SomeOtherComponent = () => (
<SomeComponent initialExpanded/>
)
// Controlling state from another component
class ExpandController extends React.Component {
state = {
expanded: false,
};
handleChange = (currentExpandedState) => {
this.setState({
expanded: !currentExpandedState,
});
}
render() {
const { expanded } = this.state;
return (
<SomeComponent
expanded={ expanded }
onChange={ this.handleChange }
/>
)
}
}
Decorating a class or stateless component with multiple decorators
If you want a component to have both the hoverable
and focusable
decorators, you can simply chain them.
import * as React from 'react';
import { hoverable, focusable } from '@vcnkit/essentials';
@focusable()
@hoverable()
class SomeClassComponent extends React.Component {
render() {
const { hovering, focused, ...rest } = this.props;
return (
<div
tabIndex={ 0 }
{ ...rest }
>
{ hovering ? 'User is hovering over this element!' : 'User is not hovering over this element' }
{ focused ? 'Element has focus' : 'Element does not have focus' }
</div>
)
}
}
And for stateless components
import { hoverable, focusable } from '@vcnkit/essentials';
/*
* ...rest will contain all the necessary event handlers. onMouseEnter, onMouseLeave, onFocus and onBlur
*/
const SomeComponent = ({ hovering, focused, ...rest }) => (
<div
tabIndex={ 0 }
{ ...rest }
>
{ hovering ? 'User is hovering over this element!' : 'User is not hovering over this element' }
{ focused ? 'Element has focus' : 'Element does not have focus' }
</div>
);
export default focusable()(hoverable()(SomeComponent))
refs
to decorated components
Attaching The decorators in @vcnkit/essentials
utilize React's forwardRef API to pass refs to the decorated components.
Using React's createRef API:
import * as React from 'react';
import { hoverable } from '@vcnkit/essentials';
@hoverable()
class MyDecoratedComponent extends React.Component {
...
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
// This will hold a ref to 'MyDecoratedComponent' instead of the decorator itself.
this.ref = React.createRef();
}
render() {
return (
<MyDecoratedComponent
ref={ this.ref }
/>
)
}
}
Or, using a callback ref:
import * as React from 'react';
import { hoverable } from '@vcnkit/essentials';
@hoverable()
class MyDecoratedComponent extends React.Component {
...
}
class MyComponent extends React.Component {
render() {
return (
<MyDecoratedComponent
ref={ ref => { this.ref = ref; } }
/>
)
}
}