Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
uncontrollable
Advanced tools
Wrap a controlled react component, to allow spcific prop/handler pairs to be uncontrolled
The 'uncontrollable' npm package is a utility for creating controlled components that can also be uncontrolled. It allows you to manage the state of a component internally while still providing the ability to control it externally via props.
Creating Uncontrolled Components
This feature allows you to create components that can manage their own state internally but can also be controlled externally via props. The `uncontrollable` function wraps your component and allows you to specify which props should be treated as controlled.
const { uncontrollable } = require('uncontrollable');
class MyComponent extends React.Component {
render() {
return <input value={this.props.value} onChange={this.props.onChange} />;
}
}
const UncontrolledComponent = uncontrollable(MyComponent, { value: 'onChange' });
// Usage
<UncontrolledComponent defaultValue="initial value" />;
Handling Multiple Controlled Props
This feature allows you to handle multiple controlled props within a single component. You can specify multiple props that should be treated as controlled, and the `uncontrollable` function will manage them accordingly.
const { uncontrollable } = require('uncontrollable');
class MyComponent extends React.Component {
render() {
return (
<div>
<input value={this.props.value} onChange={this.props.onChange} />
<input value={this.props.otherValue} onChange={this.props.onOtherChange} />
</div>
);
}
}
const UncontrolledComponent = uncontrollable(MyComponent, { value: 'onChange', otherValue: 'onOtherChange' });
// Usage
<UncontrolledComponent defaultValue="initial value" defaultOtherValue="initial other value" />;
The 'react-controlled' package provides similar functionality by allowing you to create components that can be both controlled and uncontrolled. It offers a more modern API and better integration with hooks compared to 'uncontrollable'.
The 'recompose' package offers a variety of higher-order components and utilities for managing state and props in React components. While it is more feature-rich and versatile, it can be more complex to use compared to 'uncontrollable'.
Wrap a controlled react component, to allow specific prop/handler pairs to be omited by Component consumers. Uncontrollable allows you to write pure react components, with minimal state, and then wrap them in a component that will manage state for prop/handlers if they are excluded.
npm i -S uncontrollable
If you are a bit unsure on the why of this module read the next section first. If you just want to see some real-world example, check out React Widgets which makes heavy use of this strategy.
require the module: var uncontrollable = require('uncontrollable')
uncontrollable(Component, propHandlerHash, tapsHash)
Component
: is a valid react component, such as the result of createClass
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.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.
var uncontrollable = require('uncontrollable');
var UncontrolledCombobox = uncontrollable(
Combobox,
{
value: 'onChange',
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
}
})
One of the strengths of React is its extensibility model, enabled by a common practice of pushing component state as high up the tree as possible. While great for enabling extermely flexible and easy to reason about components, this can produce a lot of boilerplate to wire components up with every use. For simple components (like an input) this is usually a matter of tying the input value
prop to a parent state property via its onChange
handler. here is an extremely common pattern:
render: function() {
return (
<input type='text'
value={this.state.value}
onChange={ e => this.setState({ value: e.target.value })}/>
)
}
This pattern moves the responsibility of managing the value
from the input to its parent and mimics "two-way" databinding. Sometimes, however, there is no need for the parent to manage the input's state directly. In that case, all we want to do is set the initial value
of the input and let the input manage it from then on. React deals with this through "uncontrolled" inputs, where if you don't indicate that you want to control the state of the input externally via a value
prop it will just do the book keeping for you.
This is a great pattern which we can make use of in our own Components. It is often best to build each component to be as stateless as possible, assuming that the parent will want to control everything that makes sense. Take a simple Dropdown component as an example
var SimpleDropdown = React.createClass({
propTypes: {
value: React.PropTypes.string,
onChange: React.PropTypes.func,
open: React.PropTypes.bool,
onToggle: React.PropTypes.func,
},
render: function() {
return (
<div>
<input
value={this.props.value}
onChange={ e => this.props.onChange(e.target.value)}
/>
<button onClick={ e => this.props.onToggle(!this.props.open)}>
open
</button>
{ this.props.open &&
<ul className='open'>
<li>option 1</li>
<li>option 2</li>
</ul>
}
</div>
)
}
});
Notice how we don't track any state in our simple dropdown? This is great because a consumer of our module will have the all the flexibility to decide what the behavior of the dropdown should be. Also notice our public API (propTypes), it consists of common pattern: a property we want set (value
, open
), and a set of handlers that indicate when we want them set (onChange
, onToggle
). It is up to the parent component to change the value
and open
props in response to the handlers.
While this pattern offers a excellent amount of flexibility to our consumers it also requires them to write a bunch of boilerplate code that probably won't change much from use to use. In all likelihood they will always want to set open
in response to onToggle
, and only in rare cases, want to override that behavior. This is where our controlled/uncontrolled pattern comes in.
We want to just handle the open/onToggle case ourselves if the consumer doesn't provide a open
prop (indicating that they want to control it). Rather than complicating our dropdown component will all that logic obsuring the business logic of our dropdown, we can add it later, by taking our dropdown and wrapping it inside another component that handles that for us.
uncontrollable
allows you seperate out the logic necessary to create controlled/uncontrolled inputs letting you focus on creating a completely controlled input and wrapping it later. This tends to be a lot simplier to reason about as well.
var uncontrollable = require('uncontrollable');
var UncontrollableMyInput = uncontrollable(
SimpleDropdown,
/* define the pairs ->*/
{ value: 'onChange', open: 'onToggle' })
<UncontrollableDropdown
value={this.state.val} //we can still control these props if we want
onChange={val => this.setState({ val })}
defaultopen={true} /> // or just let the UncontrollableDropdown handle it
// and we just set an initial value (or leave it out completely)!
Now we don't need to worry about the open onToggle! The returned Component will just track open
for us by assuming that it should just set open
to whatever onToggle
returns. If we do want to worry about it we can just provide open
and onToggle
props and the uncontrolled input will just pass them through.
The above is a contrived example but it allows you to wrap even more complex Components, giving you a lot of flexibiltity in the API you can offer a consumer of your Component. For every pair of prop/handlers you also get a defaultProp of the form "default[PropName]" so value
-> defaultValue
, and open
-> defaultOpen
, etc. React Widgets makes heavy use of this strategy, you can see it in action here: https://github.com/jquense/react-widgets/blob/master/src/Multiselect.jsx#L408
FAQs
Wrap a controlled react component, to allow specific prop/handler pairs to be uncontrolled
The npm package uncontrollable receives a total of 1,498,010 weekly downloads. As such, uncontrollable popularity was classified as popular.
We found that uncontrollable demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.