Idiomatic two-way bindings in React
topeka
topeka leverages react context, to create low friction input bindings for complex or nested values.
let eventValue = e => e.target.value;
class App extends React.Component {
constructor(){
super()
state = {
value: {}
}
}
render(){
return (
<div>
<BindingContext
value={this.state.value}
onChange={value => this.setState({ value }) }
>
<section>
<Binding bindTo='name.first' mapValue={eventValue}>
{events => <input type='text' placeholder='first name' {...events}/>}
</Binding>
<Binding bindTo='name.surname' mapValue={eventValue}>
{events => <input type='text' placeholder='surname'{...events}/>}
</Binding>
<Binding
bindTo='age'
mapValue={e => parseInt(eventValue(e), 10)}
>
{events => <input type='number' placeholder='age' {...events}/>}
</Binding>
</section>
</BindingContext>
<div>
<h5>current value: </h5>
<pre>{JSON.stringify(this.state.value, null, 2) }</pre>
</div>
</div>
)
}
}
The <Binding/>
components operate on the assumption that they are rendering idiomatic React inputs,
ie. controllable inputs. By default
Bindings inject a value
prop and an onChange
callback to into their child. They then take care of updating
with their surrounding BindingContext.
Composition
Bindings work with anything that accept a value and report a desired change in that value. Since BindingContext
accept a value and fire onChange
you can easily nest BindingContexts! Take the above example but with the name
branch abstracted into its own component.
let eventValue = e => e.target.value;
let Names = props => {
return (
<BindingContext {...props}>
<div>
<Binding bindTo='first' mapValue={eventValue}>
<input type='text' placeholder='first name' className='form-control'/>
</Binding>
<Binding bindTo='surname' mapValue={eventValue}>
<input type='text' placeholder='surname' className='form-control'/>
</Binding>
</div>
</BindingContext>
)
}
class App extends React.Component {
constructor(){
super()
state = {
value: {}
}
}
render(){
return (
<div>
<BindingContext
value={this.state.value}
onChange={value => this.setState({ value }) }
>
<section>
<Binding bindTo='name' mapValue={eventValue}>
<Names/>
</Binding>
<Binding
bindTo='age'
mapValue={e => parseInt(eventValue(e), 10)}
>
<input type='number' placeholder='age'/>
</Binding>
</section>
</BindingContext>
<div>
<h5>current value: </h5>
<pre>{JSON.stringify(this.state.value, null, 2) }</pre>
</div>
</div>
)
}
}