-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[UI Framework] Migrate from a handrolled interface for building examples to using React Components directly. #10259
[UI Framework] Migrate from a handrolled interface for building examples to using React Components directly. #10259
Conversation
7b4f497
to
5b40b5f
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 like this approach much better.
@@ -0,0 +1,12 @@ | |||
import ActionTypes from './action_types'; | |||
|
|||
export default { |
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 is very "un-ES6-y". This should rather be:
export openCodeViewer: source => ({
// ...
})
export closeCodeViewer: () => ({
// ...
})
Then you can import { openCodeViewer } from '...'
instead. This also makes tree shaking and other things possible (not necessarily relevant for this use case, I just think an ES6 module should almost never default export an object that just contains functions either way).
} | ||
|
||
shouldComponentUpdate(nextProps) { | ||
return nextProps !== this.props; |
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.
Have you double-check that you don't get a new ref every time here? Should probably just use React.PureComponent
instead, which does a shallow compare on props and 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.
Good call, it is always a different reference.
} | ||
|
||
componentWillMount() { | ||
this.context.registerSection(this.getId(), this.props.title); |
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.
registerSection
shouldn't be on context
, rather rely on connect
to inject these into the component instead. I would create a GuideSection
container that does it.
(context
is an escape hatch, so prefer not to use it. In this case you'd still actually depend on context
, but through react-redux instead. I think that's okey, as it's a "stricter api")
unregisterCode: this.props.unregisterCode, | ||
registerSection: this.props.registerSection, | ||
unregisterSection: this.props.unregisterSection, | ||
sections: this.props.sections, |
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.
On that context note: all of these should be removed from context and rather fetched from the redux store in containers where they are needed (so either pass them through as props all the way down, or insert containers in the tree that fetches it from the store).
…act Components directly.
5b40b5f
to
e662aaa
Compare
…ions via context.
import { GuidePage } from './guide_page.jsx'; | ||
|
||
function mapStateToProps(state, ownProps) { | ||
return Object.assign({}, ownProps, { |
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.
What's the best practice here? Should I assign the properties to ownProps
instead?
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.
In general, never mutate (so never assign to ownProps
). Also, what isn't immediately clear in react-redux is that ownProps
is automatically injected, so no need to pass it in here.
In the Cloud UI code base we'd do this:
import { getSections } from '../../reducers';
const mapStateToProps = state => ({
sections: getSections(state)
});
I've added a getSections
helper. What we learned through experience was that grabbing into the state
here makes it more difficult to move that state around in the tree later. So now we never grab into the state outside of the reducers
folder, just rely on helpers like this. That has proven very nice when "refactoring" the state layout, e.g. if you need an additional level or do some processing before returning state etc.
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 is our reducers/index.js
file: https://github.com/elastic/cloud/blob/master/cloud-ui/public/reducers/index.js
A tad long, but simple, at least. You can for example follow the flow for getRegion
to understand how it's built up through "gradual knowledge" of the state tree: https://github.com/elastic/cloud/blob/a3557255a4e65cd51897a52640c577262845ee20/cloud-ui/public/reducers/index.js#L115
|
||
function mapStateToProps(state, ownProps) { | ||
return ownProps; | ||
} |
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 a best practice here for just passing the props along?
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 the simple cases mapDispatchToProps
can just be an object that lists the actions (it's just complex cases that need the functions and use of bindActionCreators
). This is how we would write this file in Cloud UI:
import { connect } from 'react-redux';
import { GuideSection } from './guide_section.jsx';
import {
openCodeViewer,
registerSection,
unregisterSection,
} from '../../actions';
// does not need any state, receives everything through props
const mapStateToProps = null
export default connect(
mapStateToProps,
{
openCodeViewer,
registerSection,
unregisterSection,
}
)(GuideSection);
(without the comment, but that was just to make it clearer)
@kjbekkelund Thanks for the great feedback, man! Would you mind taking another look? I had a few questions about best practices. |
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.
Sweet, me likes ❤️
As far as I can see it looks 👍 now. Just some comments around how we write our containers on Cloud.
import { GuidePage } from './guide_page.jsx'; | ||
|
||
function mapStateToProps(state, ownProps) { | ||
return Object.assign({}, ownProps, { |
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.
In general, never mutate (so never assign to ownProps
). Also, what isn't immediately clear in react-redux is that ownProps
is automatically injected, so no need to pass it in here.
In the Cloud UI code base we'd do this:
import { getSections } from '../../reducers';
const mapStateToProps = state => ({
sections: getSections(state)
});
I've added a getSections
helper. What we learned through experience was that grabbing into the state
here makes it more difficult to move that state around in the tree later. So now we never grab into the state outside of the reducers
folder, just rely on helpers like this. That has proven very nice when "refactoring" the state layout, e.g. if you need an additional level or do some processing before returning state etc.
import { GuidePage } from './guide_page.jsx'; | ||
|
||
function mapStateToProps(state, ownProps) { | ||
return Object.assign({}, ownProps, { |
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 is our reducers/index.js
file: https://github.com/elastic/cloud/blob/master/cloud-ui/public/reducers/index.js
A tad long, but simple, at least. You can for example follow the flow for getRegion
to understand how it's built up through "gradual knowledge" of the state tree: https://github.com/elastic/cloud/blob/a3557255a4e65cd51897a52640c577262845ee20/cloud-ui/public/reducers/index.js#L115
|
||
function mapStateToProps(state, ownProps) { | ||
return ownProps; | ||
} |
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 the simple cases mapDispatchToProps
can just be an object that lists the actions (it's just complex cases that need the functions and use of bindActionCreators
). This is how we would write this file in Cloud UI:
import { connect } from 'react-redux';
import { GuideSection } from './guide_section.jsx';
import {
openCodeViewer,
registerSection,
unregisterSection,
} from '../../actions';
// does not need any state, receives everything through props
const mapStateToProps = null
export default connect(
mapStateToProps,
{
openCodeViewer,
registerSection,
unregisterSection,
}
)(GuideSection);
(without the comment, but that was just to make it clearer)
Thanks a ton @kjbekkelund ! This has been super informative. I also really like this structure much better than the way it was before -- it's so much more flexible and transparent. |
…act Components directly. Backports PR #10259 **Commit 1:** Migrate from a handrolled interface for building examples to using React Components directly. * Original sha: e662aaa * Authored by CJ Cenizal <[email protected]> on 2017-02-08T23:39:24Z **Commit 2:** Wrap components with containers instead of passing down state and actions via context. * Original sha: eb5d684 * Authored by CJ Cenizal <[email protected]> on 2017-02-10T02:09:21Z **Commit 3:** Simplify containers. * Original sha: 5b15502 * Authored by CJ Cenizal <[email protected]> on 2017-02-17T22:09:52Z
…act Components directly. (#10452) Backports PR #10259 **Commit 1:** Migrate from a handrolled interface for building examples to using React Components directly. * Original sha: e662aaa * Authored by CJ Cenizal <[email protected]> on 2017-02-08T23:39:24Z **Commit 2:** Wrap components with containers instead of passing down state and actions via context. * Original sha: eb5d684 * Authored by CJ Cenizal <[email protected]> on 2017-02-10T02:09:21Z **Commit 3:** Simplify containers. * Original sha: 5b15502 * Authored by CJ Cenizal <[email protected]> on 2017-02-17T22:09:52Z
Per advice from @kjbekkelund