-
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
feat(AutoControlledComponent): Default values #1066
feat(AutoControlledComponent): Default values #1066
Conversation
Current coverage is 95.80% (diff: 100%)@@ master #1066 diff @@
==========================================
Files 873 866 -7
Lines 4813 4790 -23
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
- Hits 4612 4589 -23
Misses 201 201
Partials 0 0
|
I think this sounds sane, using the initial state as the "default" AC prop values. Another way of thinking about this is that it is the "default state", since, the props are copied to state immediately. I'm gonna update this PR to undo the Accordion hacks as well just to ensure it is working correctly. |
f999d35
to
63babf3
Compare
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.
I updated the Accordion to use the new initial state pattern in the constructor. I consolidated the "default state" logic into the getAutoControlledStateValue
function as well. Finally, I added another test to cover an edge case.
LMK if anything looks out of place.
// The default prop should always win on first render. | ||
// This default check should then be removed. | ||
if (typeof this.props.defaultActiveIndex === 'undefined') { | ||
this.trySetState({ activeIndex: this.props.exclusive ? -1 : [-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.
I completed this todo, using the new proposed standard of setting initial state in the constructor.
// defaultProps & props | ||
if (includeDefaultProps && !hasProp && hasDefaultProp) return defaultProp | ||
if (hasProp) return prop | ||
export const getAutoControlledStateValue = (propName, props, state, includeDefaults = false) => { |
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 this function to handle initial state. Since this is the SSOT when it comes to how we derive AC state I think it makes sense here. It also prevents an extra loop of state in CWM.
@@ -32,6 +32,11 @@ describe('extending AutoControlledComponent', () => { | |||
TestClass = createTestClass({ autoControlledProps: [], state: {} }) | |||
}) | |||
|
|||
it('does not throw with a `null` state', () => { |
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.
Extra test to catch the edge case I ran into. If you have state = undefined
React will initialize state as null
. This then throws as we look for keys on null
. I added a comment about this and a check for it in getAutoControlledStateValue
.
|
||
// initial state | ||
// check for an object as React initializes state as `null` if it is `undefined` | ||
const initialState = ({}).toString.call(state) === '[object Object]' && state[propName] |
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.
I'd maybe do something like:
if (state) {
const initialState = state[propName]
if (initialState !== undefined) return initialState
}
state
will only every be: an object, null, or undefined. I think this is a more straightforward check than thetoString
check.if (initialState) return initialState
wouldn't letnull
through, which should be a valid default value.- It makes it the same style as the
propValue
anddefaultProp
returns.
Left a comment but otherwise I think this is g2g 👍 |
I try to avoid extra nesting / branch logic when possible. Perhaps went too far in that case, updated :) |
Released in |
Fixes #764
I noticed a bug using
AutoControlledComponent
where initial state for non-AutoControlled props was getting clobbered in thecomponentWillMount
of the ACC. This is because ACC usesthis.state = ...
instead ofthis.setState
to set the state.Example of the bug:
In fixing that bug, I came across the #764 issue which I realized could be solved simultaneously. The initial state is set within the constructor which means it's available in the ACC's
componentWillMount
. I updated thecomponentWillMount
to use the initial state if the value of the prop was not defined via props.For example:
This seems pretty intuitive to me. The props are being passed directly to state, so to specify a "global default" outside of props, you set the state yourself in the constructor (or use the es7 property initializer, which is basically just sugar on the same thing).
Edit As an aside, I've been using
AutoControlledComponent
as a base for some of my own components via:I think it's a really cool abstraction and lets you write super flexible/reusable components that either do all the work (control the state) or let the user do it from higher up.
@levithomason - Thoughts on open-sourcing it as a separate package? Its only dependency is
lodash
(which you could probably even drop) so would probably be pretty simple.