immreact
Version:
Use react classes with an immstruct structure
226 lines (197 loc) • 5.58 kB
JavaScript
/**
* Stateful components
* ---
* The Counter components create their own internal state when they are created.
* They must be passed a reference to their root from which they can grab fresh
* cursors when necessary, this keeps everything synced correctly. They then
* create their own state containers and keep a reference to it, and are able
* to delete themselves when they unmount.
*
* The App component here simply keeps track of how many counters are currently
* active. In the real world an example such as this would probably just pass
* props to each counter, but, more complex examples might then add internal
* state to components.
*/
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import element from '../_common/element'
import ActionButton from '../_common/actionButton'
import state from './state'
window.state = state
const styles = {}
var id = 0
/**
* Counter
*/
class Counter extends Component {
static style = {
counter: {
border: '1px solid rgb( 232, 232, 232)',
borderRadius: 6,
overflow: 'hidden',
marginBottom: 12
},
header: {
background: 'rgb( 235, 137, 49 )',
fontSize: 13,
color: 'rgb( 255, 255, 255 )',
fontWeight: 600,
textTransform: 'uppercase',
padding: '3px 12px'
},
container: {
display: 'flex'
},
left: {
flex: .5,
padding: 12,
background: 'rgb( 255, 255, 255 )'
},
right: {
flex: .5,
background: 'rgb( 40, 40, 40 )',
color: 'rgb( 244, 246, 252 )',
padding: 12
},
codeHeader: {
fontSize: 17,
marginTop: 0,
marginBottom: 12
},
code: {
fontFamily: 'Source Code Pro, consolas, monospace',
fontSize: 15,
margin: 0
},
count: {
fontSize: 44,
textAlign: 'center',
display: 'block',
marginBottom: 12
}
};
constructor( props ) {
super( props )
// ids should be unique, for brevity just increment a global to ensure uniqueness
this.id = id++ + ''
// Create a new root for this component
this.props.root.cursor().update( cursor => {
return cursor.merge({
[ this.id ]: {
count: 0
}
})
})
// Grab a reference to the new data
this.state = this.props.root.reference( this.id )
}
componentWillUnmount() {
this.props.root.cursor().update( cursor => {
return cursor.delete( this.id )
})
}
onAdd = event => {
this.state.cursor( 'count' ).update( cursor => ++cursor )
};
onSubtract = event => {
this.state.cursor( 'count' ).update( cursor => --cursor )
};
render() {
// Grab a fresh cursor from the reference
let state = this.state.cursor()
return (
<div style={ Counter.style.counter }>
<header style={ Counter.style.header }>
<span>Counter { this.id }</span>
</header>
<div style={ Counter.style.container }>
<div style={ Counter.style.left }>
<span style={ Counter.style.count }>{ state.get( 'count' ) }</span>
<ActionButton onClick={ this.onAdd }>+</ActionButton>
<ActionButton onClick={ this.onSubtract }>-</ActionButton>
</div>
<div style={ Counter.style.right }>
<p style={ Counter.style.codeHeader }><code>Counter</code> component state</p>
<pre style={ Counter.style.code }>
{ JSON.stringify( state.toJSON(), null, ' ' ) }
</pre>
</div>
</div>
</div>
)
}
}
class App extends Component {
static style = {
container: {
display: 'flex'
},
left: {
flex: .5,
padding: '1em'
},
right: {
flex: .5,
minHeight: '100vh',
borderLeft: '1px solid rgb( 232, 232, 232)',
background: 'rgb( 40, 40, 40 )',
padding: '1em'
},
code: {
fontFamily: 'Source Code Pro, consolas, monospace',
fontSize: 15,
color: 'rgb( 244, 246, 252 )',
margin: 0
},
controls: {
marginBottom: '1em'
}
};
constructor( props ) {
super( props )
}
addCounter = event => {
this.props.state.cursor( 'counters' ).update( cursor => ++cursor )
};
removeCounter = event => {
this.props.state.cursor( 'counters' ).update( cursor => --cursor )
};
addMultiple = event => {
this.props.state.cursor( 'counters' ).update( cursor => cursor += 3 )
};
render() {
let counters = []
for ( let i = 0; i < this.props.state.cursor( 'counters' ).deref(); i++ ) {
counters.push( <Counter key={ 'counter' + i } root={ this.props.state } /> )
}
return (
<div style={ App.style.container }>
<div style={ App.style.left }>
<div style={ App.style.controls }>
<ActionButton onClick={ this.addCounter }>Add</ActionButton>
<ActionButton onClick={ this.addMultiple }>Add Multiple</ActionButton>
<ActionButton onClick={ this.removeCounter }>Remove</ActionButton>
</div>
{ counters }
</div>
<div style={ App.style.right } onClick={ this.addCounter }>
<pre style={ App.style.code }>
{ JSON.stringify( state.get().toJSON(), null, ' ' ) }
</pre>
</div>
</div>
)
}
}
/**
* Main app render function
*/
function render() {
ReactDOM.render( <App state={ state.reference( 'root' ) } />, element )
}
/**
* Attach listener to re-render on state changes
*/
state
.on( 'update', render )
.start()