-
Notifications
You must be signed in to change notification settings - Fork 4k
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(MountNode): add component #2407
Conversation
07a3686
to
c4c3dec
Compare
`${config.paths.src()}/collections/**/*.js`, | ||
`${config.paths.src()}/modules/**/*.js`, | ||
`${config.paths.src()}/views/**/*.js`, | ||
`${config.paths.src()}/addons/*/*.js`, |
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 think that we need to store component specific functionality in its directory, this will allow to manage easier in future, see #1443.
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.
Yep, looks like we already have a flat structure any how.
@layershifter Please consider the changes made to test/specs/modules/Modal/Modal-test.js in #2394. It fixes an invisible issue in the tests that were made visible by my proposed changes. The issue is that the modals are never unmounted, thus modals mounted in one tests interacts with modals mounted in another test. This was provoking various seemingly unrelated modal tests to fails. |
@layershifter Also, your approach to fix this issue looks promising! |
src/addons/MountNode/MountNode.js
Outdated
shouldComponentUpdate({className: next}) { | ||
const {className: current} = this.props | ||
|
||
return next !== current |
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 know we've started this pattern of renaming destructured keys elsewhere but I'd prefer more explicit names like nextClassName
and currClassName
.
_.map(_.split(/\s+/)), | ||
_.flatten, | ||
_.uniq, | ||
)([...components]) |
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 didn't test this, but I'm pretty sure:
- Lodash will gracefully handle null/undefined args so no need to check if
components
exists first - Lodash will not mutate the original array so no need copy if first
- There is a
_.flatMap
that will handle mapping and flattening at the same time. - This will currently include empty string
className
s. However, switching the filter to an_.identity
will filter all falsy values.
This can probably be simplified to:
const computeClasses = _.flow(
_.map('props.className'),
_.filter(_.identity),
_.flatMap(_.split(/\s+/)),
_.uniq,
)
Which works like so:
computeClasses(null) //=> []
computeClasses(undefined) //=> []
computeClasses([
null,
undefined,
{},
{ props: {} },
{ props: { className: null } },
{ props: { className: undefined } },
{ props: { className: 'foo' } },
{ props: { className: 'foo bar' } },
{ props: { className: '0' } },
{ props: { className: 'false' } },
])
// => [ 'foo', 'bar', '0', 'false' ]
src/modules/Modal/Modal.js
Outdated
} | ||
|
||
if (this.state.mountClasses !== classes) newState.mountClasses = classes | ||
console.log(classes) |
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.
Whoops
src/modules/Modal/Modal.js
Outdated
} | ||
|
||
if (this.state.mountClasses !== classes) newState.mountClasses = classes | ||
console.log(classes) | ||
if (Object.keys(newState).length > 0) this.setState(newState) |
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.
For consistency, how about this?
if (!_.isEmpty(newState)) this.setState(newState)
src/modules/Modal/Modal.js
Outdated
@@ -296,6 +284,8 @@ class Modal extends Component { | |||
if (!childrenUtils.isNil(children)) { | |||
return ( | |||
<ElementType {...rest} className={classes} style={{ marginTop, ...style }} ref={this.handleRef}> | |||
<MountNode className={mountClasses} node={mountNode || document.body} /> |
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.
Whoops, accessing document.body
will break SSR. Looks like Modal has a method for this purpose, this.getMountNode()
.
src/modules/Modal/Modal.js
Outdated
@@ -304,6 +294,8 @@ class Modal extends Component { | |||
|
|||
return ( | |||
<ElementType {...rest} className={classes} style={{ marginTop, ...style }} ref={this.handleRef}> | |||
<MountNode className={mountClasses} node={mountNode || document.body} /> |
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.
See above SSR comment.
import _ from 'lodash' | ||
import computeClasses from './computeClasses' | ||
|
||
let prevClasses = [] |
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.
This will be shared across all MountNode
instances. If two MountNodes are on the page, they will need their own copy of prevClasses
to diff against, correct?
This might belong on the actual MountNode class so it can be instance specific.
Oh boy, just noticed this wasn't ready for review 🤦♂️ Well, hopefully, the comments are of some help and not too intrusive! Sorry about that... |
@levithomason your feedback is awesome as always, will back there soon 👍 |
c4c3dec
to
ed80f05
Compare
…React into feat/body # Conflicts: # index.d.ts # src/index.js # src/lib/customPropTypes.js
Codecov Report
@@ Coverage Diff @@
## master #2407 +/- ##
==========================================
+ Coverage 99.74% 99.74% +<.01%
==========================================
Files 154 160 +6
Lines 2712 2745 +33
==========================================
+ Hits 2705 2738 +33
Misses 7 7
Continue to review full report at Codecov.
|
@levithomason now it's ready for review 😸 |
import computeClassNames from './computeClassNames' | ||
import computeClassNamesDifference from './computeClassNamesDifference' | ||
|
||
const prevClassNames = new Map() |
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.
Should this not also be a property on each instance of a MountNode? Currently, this map is shared between all MountNode instances, over the life of the app. So as they mount/unmount, this same map will be reused.
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.
Yes, it's a planned solution. We use a DOM node as the key in Map, so we will have the same classes for the same node.
Here is a simple example:
const div = document.createElement('div')
const Tree = () => (
<>
<MountNode className='foo' node={div} />
<MountNode className='bar' node={div} />
</>
)
Because we have the same Map, we will have foo bar
className on the div
element after Tree
will be mounted.
Just one final question on the use of a global Map. |
@levithomason ping |
Released in |
* feat(MountNode): add component * restore Responsive test * test(Modal): fix test
Fixes #2248.
Replaces #2394, #2255.
Core problem
Our
Modal
component makes too many things, the main problem is the management ofclassList
onmountNode
. It made too many problem for us, I thought about it and here is solution.Solution
We need a declative way for
classList
management onmountNode
, I've took a look on react-document-title and made similar component.