Skip to content
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

Merged
merged 6 commits into from
Feb 18, 2018
Merged

feat(MountNode): add component #2407

merged 6 commits into from
Feb 18, 2018

Conversation

layershifter
Copy link
Member

@layershifter layershifter commented Jan 5, 2018

Fixes #2248.
Replaces #2394, #2255.

Core problem

Our Modal component makes too many things, the main problem is the management of classList on mountNode. It made too many problem for us, I thought about it and here is solution.

Solution

We need a declative way for classList management on mountNode, I've took a look on react-document-title and made similar component.

`${config.paths.src()}/collections/**/*.js`,
`${config.paths.src()}/modules/**/*.js`,
`${config.paths.src()}/views/**/*.js`,
`${config.paths.src()}/addons/*/*.js`,
Copy link
Member Author

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.

Copy link
Member

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.

@guillaumep
Copy link

guillaumep commented Jan 6, 2018

@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.

@guillaumep
Copy link

@layershifter Also, your approach to fix this issue looks promising!

shouldComponentUpdate({className: next}) {
const {className: current} = this.props

return next !== current
Copy link
Member

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])
Copy link
Member

@levithomason levithomason Jan 10, 2018

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 classNames. 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' ]

}

if (this.state.mountClasses !== classes) newState.mountClasses = classes
console.log(classes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops

}

if (this.state.mountClasses !== classes) newState.mountClasses = classes
console.log(classes)
if (Object.keys(newState).length > 0) this.setState(newState)
Copy link
Member

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)

@@ -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} />
Copy link
Member

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().

@@ -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} />
Copy link
Member

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 = []
Copy link
Member

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.

@levithomason
Copy link
Member

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...

@layershifter
Copy link
Member Author

@levithomason your feedback is awesome as always, will back there soon 👍

@codecov-io
Copy link

codecov-io commented Jan 13, 2018

Codecov Report

Merging #2407 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
src/addons/MountNode/lib/computeClassNames.js 100% <100%> (ø)
src/addons/MountNode/lib/handleClassNamesChange.js 100% <100%> (ø)
...ddons/MountNode/lib/computeClassNamesDifference.js 100% <100%> (ø)
src/modules/Modal/Modal.js 100% <100%> (ø) ⬆️
src/addons/MountNode/MountNode.js 100% <100%> (ø)
src/addons/MountNode/lib/getNodeFromProps.js 100% <100%> (ø)
src/addons/MountNode/lib/NodeRegistry.js 100% <100%> (ø)
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3f26bef...64c6942. Read the comment docs.

@layershifter
Copy link
Member Author

@levithomason now it's ready for review 😸

import computeClassNames from './computeClassNames'
import computeClassNamesDifference from './computeClassNamesDifference'

const prevClassNames = new Map()
Copy link
Member

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.

Copy link
Member Author

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.

@levithomason
Copy link
Member

Just one final question on the use of a global Map.

@layershifter
Copy link
Member Author

@levithomason ping

@levithomason levithomason merged commit 0c5e2f9 into master Feb 18, 2018
@levithomason
Copy link
Member

Released in semantic-ui-react@0.78.3.

Brantron pushed a commit to Brantron/Semantic-UI-React that referenced this pull request Mar 14, 2018
* feat(MountNode): add component

* restore Responsive test

* test(Modal): fix test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants