uncontrollable
Advanced tools
Comparing version 1.2.0 to 1.3.0
"use strict"; | ||
var babelHelpers = require("./util/babelHelpers.js"); | ||
var React = require("react"); | ||
var ReactUpdates = require("react/lib/ReactUpdates"); | ||
var invariant = require("react/lib/invariant"); | ||
@@ -30,2 +31,10 @@ | ||
function forceUpdateIfMounted() { | ||
if (this.isMounted() && this._needsUpdate) { | ||
this._needsUpdate = false; | ||
this.forceUpdate(); | ||
} | ||
} | ||
module.exports = function (Component, controlledValues, taps) { | ||
@@ -58,3 +67,7 @@ var name = Component.displayName || Component.name || "Component", | ||
getInitialState: function () { | ||
componentWillMount: function () { | ||
var _this = this; | ||
this.values = Object.create(null); | ||
var props = this.props, | ||
@@ -64,9 +77,8 @@ keys = Object.keys(controlledValues); | ||
return transform(keys, function (state, key) { | ||
state[key] = props[defaultKey(key)]; | ||
_this.values[key] = props[defaultKey(key)]; | ||
}, {}); | ||
}, | ||
shouldComponentUpdate: function () { | ||
//let the setState trigger the update | ||
return !this._notifying; | ||
componentWillReceiveProps: function (nextProps) { | ||
this._needsUpdate = false; | ||
}, | ||
@@ -91,3 +103,3 @@ | ||
newProps[propName] = prop !== undefined ? prop : _this.state[propName]; | ||
newProps[propName] = prop !== undefined ? prop : _this.values[propName]; | ||
@@ -99,3 +111,2 @@ newProps[handle] = setAndNotify.bind(_this, propName); | ||
//console.log('props: ', newProps) | ||
each(taps, function (val, key) { | ||
@@ -116,21 +127,13 @@ return newProps[key] = chain(_this, val, newProps[key]); | ||
handler = this.props[controlledValues[propName]]; | ||
//, controlled = handler && isProp(this.props, propName); | ||
if (linkName && isProp(this.props, linkName) && !handler) { | ||
handler = this.props[linkName].requestChange | ||
//propName = propName === 'valueLink' ? 'value' : 'checked' | ||
; | ||
handler = this.props[linkName].requestChange; | ||
} | ||
if (handler) { | ||
this._notifying = true; | ||
handler.call.apply(handler, [this, value].concat(args)); | ||
this._notifying = false; | ||
} | ||
if (handler) handler.call.apply(handler, [this, value].concat(args)); | ||
this.setState((function () { | ||
var _setState = {}; | ||
_setState[propName] = value; | ||
return _setState; | ||
})()); | ||
this.values[propName] = value; | ||
this._needsUpdate = true; | ||
ReactUpdates.asap(forceUpdateIfMounted, this); | ||
} | ||
@@ -171,3 +174,2 @@ | ||
return o ? Object.prototype.hasOwnProperty.call(o, k) : false; | ||
} | ||
//return !controlled | ||
} |
{ | ||
"name": "uncontrollable", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "Wrap a controlled react component, to allow spcific prop/handler pairs to be uncontrolled", | ||
@@ -50,2 +50,3 @@ "author": { | ||
"react-hot-loader": "^1.1.7", | ||
"react-layer": "^1.1.0", | ||
"rimraf": "^2.2.8", | ||
@@ -52,0 +53,0 @@ "sinon-chai": "^2.6.0", |
@@ -21,3 +21,2 @@ # uncontrollable | ||
- `propHandlerHash`: define the pairs of prop/handlers you want to be uncontrollable eg. `{ value: 'onChange'}` | ||
- `tapsHash`: sometimes you need to jump in the middle of a handler to adjust the value of another prop. You can specify a tap function to run before the handler like: `{ onToggle: function(){ this.setState({value: null }) }`. `this` in the tab will be the uncontrolled component instance, you can use it to adjust its own internal state (`setState` calls will not trigger duplicate renders). This is generally not recommended if it can be avoided, but sometimes it is necessary to deal with interactions of multiple controlled/uncontrolled pairs in a component. | ||
@@ -32,11 +31,5 @@ For ever prop you indicate as uncontrollable, the returned component will also accept an initial, `default` value for that prop. For example, `open` can be left uncontrolled but the initial value can be set via `defaultOpen={true}` if we want it to start open. | ||
{ | ||
value: 'onChange', | ||
value: ['onChange', (e, current) => !v, | ||
open: 'onToggle', | ||
searchTerm: 'onSearch' //the current typed value (maybe it filters the dropdown list) | ||
}, | ||
{ | ||
'onToggle': function (isOpen){ | ||
if ( !isOpen && this.props.searchTerm === undefined ) // if the consumer is not controlling searchTerm | ||
this.setState({ searchTerm: '' }) // reset the filter on close | ||
} | ||
}) | ||
@@ -43,0 +36,0 @@ ``` |
@@ -30,4 +30,6 @@ 'use strict'; | ||
global.expect = chai.expect | ||
var testsContext = require.context("./test", true); | ||
testsContext.keys().forEach(testsContext); |
'use strict'; | ||
var React = require('react/addons') | ||
var uncontrol = require('../src/uncontrollable') | ||
var Layer = require('react-layer') | ||
@@ -28,13 +29,19 @@ var TestUtils = React.addons.TestUtils | ||
onToggle: React.PropTypes.func, | ||
onRender: React.PropTypes.func, | ||
}, | ||
render() { | ||
if ( this.props.onRender ) | ||
this.props.onRender(this.props) | ||
return ( | ||
<div> | ||
<button onClick={this.props.onToggle}>toggle</button> | ||
{ this.props.open && | ||
{ this.props.open && | ||
<span className='open'>open!</span> | ||
} | ||
<input className='valueInput' | ||
value={this.props.value} | ||
value={this.props.value} | ||
onChange={ e => this.props.onChange(e.value)}/> | ||
@@ -54,3 +61,3 @@ <input type='checkbox' | ||
, instance = render(<Control value={3}/>) | ||
warn.should.have.been.CalledOnce; | ||
@@ -70,3 +77,3 @@ | ||
, input = findAllTag(instance, 'input')[0] | ||
input.getDOMNode().value.should.equal('10') | ||
@@ -84,3 +91,3 @@ | ||
, input = findAllTag(instance, 'input')[1] | ||
input.getDOMNode().checked.should.equal(false) | ||
@@ -107,10 +114,10 @@ | ||
it('should track state if no specified', () => { | ||
it('should track internally if not specified', () => { | ||
var Control = uncontrol(Base, { value: 'onChange' }) | ||
, instance = render(<Control />) | ||
, input = findAllTag(instance, 'input')[0] | ||
trigger.change(input.getDOMNode(), { value: 42}) | ||
instance.state.should.have.property('value') | ||
expect(instance.values).to.have.property('value') | ||
.that.equals(42) | ||
@@ -124,3 +131,3 @@ }) | ||
, span = findClass(instance, 'open') | ||
input.getDOMNode().value.should.equal('10') | ||
@@ -130,5 +137,106 @@ | ||
instance.state.value.should.equal(42) | ||
expect(instance.values.value).to.equal(42) | ||
}) | ||
it('should update in the right order when controlled', () => { | ||
var Control = uncontrol(Base, { value: 'onChange' }) | ||
, spy = sinon.spy(); | ||
var Parent = React.createClass({ | ||
getInitialState(){ return { value: 5 } }, | ||
render(){ | ||
return ( | ||
<Control | ||
onRender={spy} | ||
value={this.state.value} | ||
onChange={value => this.setState({ value })} | ||
/> | ||
) | ||
} | ||
}) | ||
var instance = render(<Parent/>) | ||
, input = findAllTag(instance, 'input')[0] | ||
trigger.change(input.getDOMNode(), { value: 42 }) | ||
spy.callCount.should.equal(2) | ||
spy.firstCall.args[0].value.should.equal(5) | ||
spy.secondCall.args[0].value.should.equal(42) | ||
}) | ||
it('should update in the right order when uncontrolled', () => { | ||
var Control = uncontrol(Base, { value: 'onChange' }) | ||
, spy = sinon.spy(); | ||
var Parent = React.createClass({ | ||
getInitialState(){ return { value: 5 } }, | ||
render(){ | ||
return ( | ||
<Control | ||
onRender={spy} | ||
defaultValue={this.state.value} | ||
/> | ||
) | ||
} | ||
}) | ||
var instance = render(<Parent/>) | ||
, input = findAllTag(instance, 'input')[0] | ||
trigger.change(input.getDOMNode(), { value: 42 }) | ||
spy.callCount.should.equal(2) | ||
spy.firstCall.args[0].value.should.equal(5) | ||
spy.secondCall.args[0].value.should.equal(42) | ||
}) | ||
it('should update correctly in a Layer', () => { | ||
var Control = uncontrol(Base, { value: 'onChange' }) | ||
, spy = sinon.spy(); | ||
var Parent = React.createClass({ | ||
getInitialState(){ return { value: 5 } }, | ||
componentWillUnmount () { | ||
this._layer.destroy() | ||
this._layer = null | ||
}, | ||
componentDidUpdate(){this._renderOverlay()}, | ||
componentDidMount() {this._renderOverlay()}, | ||
_renderOverlay() { | ||
if (!this._layer) | ||
this._layer = new Layer(document.body, ()=> this._child) | ||
this.layerInstance = this._layer.render() | ||
}, | ||
render(){ | ||
this._child = ( | ||
<Control | ||
onRender={spy} | ||
value={this.state.value} | ||
onChange={value => this.setState({ value, called: true })} | ||
/> | ||
) | ||
return ( | ||
<div/> | ||
) | ||
} | ||
}) | ||
var instance = render(<Parent/>) | ||
, input = findAllTag(instance.layerInstance, 'input')[0] | ||
trigger.change(input.getDOMNode(), { value: 42 }) | ||
spy.callCount.should.equal(2) | ||
spy.firstCall.args[0].value.should.equal(5) | ||
spy.secondCall.args[0].value.should.equal(42) | ||
}) | ||
describe('taps', () => { | ||
@@ -162,2 +270,1 @@ | ||
}) | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
194219
4728
29
111