Electrum
Electrum simplifies framework-agnostic declaration of React components.
It is used internally by Epsitec SA to
bridge the gap with its Xcraft toolchain and with its Lydia framework.
Where does the name Electrum come from?
Electrum is an alloy of gold and
silver used to produce ancient Lydian
coinage.
The first metal coins ever made, were of Electrum and date back to the end
of the 7th century, beginning of the 6th century BC.
THIS IS WORK IN PROGRESS
The implementation of electrum
is being modified radically.
Please wait until version has stabilized.
Linking components with their state
Let's say we want to display an article which contains the content
and information about the author. The article data might be represented
like this:
{ "article":
{ "content":
{ "title": "About typography"
, "text": "Lorem ipsum..."
, "date": "2015-12-02" }
, "author":
{ "name": "John"
, "mail": "john@doe.org" } } }
This can be loaded into a store
instance. The "article"
node will be
passed as state to the <Article>
component:
const state = store.select ('article');
return <Article state={state}/>;
The <Article>
can be implemented as a stateless function component:
import E from 'electrum';
function Article (props) {
return (
<div>
<Content {...E.link (props, 'content')} />
<Author {...E.link (props, 'author')} />
</div>
);
}
Reading state
Components will very often need to read values from the store. To make life
easier for the developer, electrum
provides a read()
method, which takes
the props
of the component and the id
of the value to read:
import E from 'electrum';
function Content (props) {
return (
<div>
<h1>{E.read (props, 'title')}</h1>
<p>{E.read (props, 'text')}</p>
</div>
);
}
Managing styles with Radium
We decided to use radium
as the way to go to inject styles into components.
By using the E
instance provided by import Electrum from 'electrum'
),
components are automatically configured to use radium
when wrapped like
this:
import Electrum from 'electrum';
import _Button from './Button.component.js';
import _Button$styles from './Button.styles.js';
export const Button = Electrum.wrap ('Button', _Button, {styles: _Button$styles});
Wrapping and automatic component extensions
Electrum.wrap()
returns a new component class
, which will be treated as a
pure component by React:
shouldComponentUpdate(nextProps, nextState)
→ pure component.
The test is based on a shallow comparison of the properties and of the state
(if any).
It injects some additional functionality:
link(id)
→ shorthand for Electrum.link(this.props, id)
.read(id)
→ shorthand for Electrum.read(this.props, id)
.theme
→ shorthand for this.props.theme
.styles
→ resolves the styles based on rules implemented by Style
.
The component is also extended by Radium
which will flatten styles
arrays injected in child components, and handle the
state required to handle browser states such as :hover
.
Sending events to the bus
Electrum can use a bus to dispatch messages/commands and notify changes.
The bus interface looks like this:
{
dispatch (props, message) {}
notify (props, value, ...states) {}
}
Bus configuration
A bus can be attached with Electrum.useBus(bus)
.
Event forwarding
The default Electrum
instance is configured to use electrum-events
,
which injects various event handlers into the wrapped components:
onChange
onKeyDown
, onKeyUp
, onKeyPress
onFocus
Note: if the component provides its own event handlers, they will be
called by the injected methods.
Events will automatically be sent to the bus, if one has been configured
(see Electrum.use
). The EventHandlers
class in electrum-events
is
in charge of the event forwarding. It will provide the value and the
states associated with the underlying component, usually by reading
the DOM:
value
← event.target.value
states
← {begin:0, end:10}
for text fields
Custom value or states
When the defaults are not meaningful (e.g. for a checkbox
, where the
value does not exist per se), the component can provide the value
(method getValue()
) or the states (method getStates()
):
class MyCheckbox extends React.Component {
render () {
return <input type='checkbox' /* ... */ />;
}
getValue (target) {
return target.checked ? 'on' : 'off';
}
}
Automating component wrapping
The easiest way to get all components of a module wrapped is to use the
electrum-require-components
module.
See electrum-require-components.
Install electrum-require-components
npm install electrum-require-components --save-dev
Configure your package
Edit package.json
to add a script that can be invoked with npm run regen
in order to regenerate the source file all.js
which includes, wraps and exports
all components.
"scripts": {
...
"regen": "electrum-require-components --wrap ./src components .component.js all.js"
}
Export all wrapped components
To export all components found in your module, use:
export * from './all.js';
Note: as of December 4 2015, Babel 6 chokes on source files with only
a wildcard export. See this issue.
The error message is:
Invariant Violation: src/components.js: To get a node path the parent
needs to exist
A workaround is to first import, then export:
import * as all from './all.js';
export * from './all.js';