-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AutoControlledComponent #224
Conversation
95fa4e0
to
92c068d
Compare
@@ -35,6 +38,9 @@ module.exports = (karmaConfig) => { | |||
...webpackConfig.module.loaders, | |||
], | |||
}, | |||
plugins: [ | |||
new webpack.DefinePlugin(config.compiler_globals), | |||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needed compiler globals for __PROD__
use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommend using process.env.NODE_ENV
. It's more verbose, but is expected in webpack builds nowadays (esp. for React), whereas the former is just an internal helper. This way users won't have to have __PROD__
as a global.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a task to the main PR. Will handle in the final cleanup/refactor.
92c068d
to
52a183e
Compare
@@ -0,0 +1,44 @@ | |||
/* eslint-disable no-console */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review this file, this is identical to actual component, but with no comments nor run time warnings. It is only the production code. This makes it about 1/3 the size and much easier to grok.
52a183e
to
143a28a
Compare
import _ from 'lodash' | ||
import { Component } from 'react' | ||
|
||
const toDefault = (prop) => `default${prop.slice(0, 1).toUpperCase() + prop.slice(1)}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.slice(0, 1)
=== prop[0]
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't school me publicly.... 😝
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated!
143a28a
to
51d4518
Compare
} | ||
|
||
componentWillReceiveProps(nextProps) { | ||
if (super.componentWillReceiveProps) super.componentWillReceiveProps(nextProps) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there ever a case where super.componentWillReceiveProps
would ever return falsy given super
represents React.Component
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this seems intuitive. However, when removing the checks we get this in tests:
undefined is not an object (evaluating '_get(Object.getPrototypeOf(AutoControlledComponent.prototype), 'componentWillReceiveProps', this).call')
I'm not sure how React is constructing these classes, but apparently they do not have all their methods at least in tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note, I added the missing check/call to the componentWillMount()
as well.
db5150b
to
86892f6
Compare
⛵ |
if (super.componentWillMount) super.componentWillMount() | ||
const { autoControlledProps } = this.constructor | ||
|
||
this.state = _.transform(autoControlledProps, (res, prop) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldw be cool to get some comments around this. It took me a couple minutes to fully grok it especially due to the generically named to toDefault
. Not at all a blocker, just a note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
react-controllables went with fromDefaultName
, I'm indifferent but want it to make the most sense to the most people.
86892f6
to
05e13eb
Compare
👻 - still a bit weary of the state overrides in |
05e13eb
to
3ee65ad
Compare
05e13eb
to
74e7926
Compare
Addressed all comments in slim file and removed it, merging after tests pass. |
AutoControlledComponent
This PR is a breakout of #206. This PR is actually tiny. It is mostly runtime warnings, tests, and comments. I've committed a "slim" version of the component with no runtime warnings nor comments so it can be reviewed easier. It is only 34 SLOC. I will remove it before merging.
Why this thing?
Many of our components have props that are possibly controlled by the consumer. Many of those props should also be self managed inside the component if there is no prop given.
Example,
<Dropdown />
has avalue
prop. This is thevalue
of the active item. When the user clicks on an item, we set the value on state internally. However, the consumer may have added a value prop to the<Dropdown />
as well. Now, when we want to access the value, we need to conditionally choose between which value we retrieve. The prop or the state?Also, it would be responsible to never set state for the value if we were passed a value prop. This saves a render. Yet, another conditional, if there is no value prop then set state. This quickly becomes ugly and hard to maintain.
Community Options
We reviewed and tested other libraries solving this same problem:
These didn't work for us for the following:
Solution
What we really want is a smart setState() that submits to controlled props.
This solves all our issues. AutoControlledComponent is extended by your component, giving it a
trySetState
method and staticautoControlledProps
array.static autoControlledProps = []
This array defines which props are auto controlled.
props
tostate
Auto controlled props are made available on
state
. The value will automatically defer to prop values if present (controlled) or the internal state value if there is no prop (uncontrolled).trySetState(maybeState, [state])
maybeState
When trying to set maybe state, controlled prop values always win. Unnecessary renders are also prevented.
state
Allows setting regular state while simultaneously trying to set state for controlled values. You often need to set both, this allows a single call to setState and a single render.
Niceties
Auto default props
defaultFoo
Each auto controlled prop also accepts a
default*
prop. It works exactly like vanilla React, only applied on first render.Runtime Warnings
The majority of the code is actually run time warnings to ensure proper use of the abstraction. Warnings are removed in production by dead code elimination.