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

[UI Framework] Migrate from a handrolled interface for building examples to using React Components directly. #10259

Conversation

cjcenizal
Copy link
Contributor

@cjcenizal cjcenizal commented Feb 9, 2017

Per advice from @kjbekkelund

@cjcenizal cjcenizal force-pushed the improvement/ui-framework-docs-with-components branch 4 times, most recently from 7b4f497 to 5b40b5f Compare February 9, 2017 16:09
@cjcenizal cjcenizal added v5.4.0 v6.0.0 Team:Platform-Design Team Label for Kibana Design Team. Support the Analyze group of plugins. labels Feb 9, 2017
@cjcenizal cjcenizal requested a review from kimjoar February 9, 2017 21:55
Copy link
Contributor

@kimjoar kimjoar left a 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 {
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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,
Copy link
Contributor

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

@cjcenizal cjcenizal force-pushed the improvement/ui-framework-docs-with-components branch from 5b40b5f to e662aaa Compare February 10, 2017 00:19
import { GuidePage } from './guide_page.jsx';

function mapStateToProps(state, ownProps) {
return Object.assign({}, ownProps, {
Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor

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;
}
Copy link
Contributor Author

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?

Copy link
Contributor

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)

@cjcenizal
Copy link
Contributor Author

@kjbekkelund Thanks for the great feedback, man! Would you mind taking another look? I had a few questions about best practices.

Copy link
Contributor

@kimjoar kimjoar left a 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, {
Copy link
Contributor

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, {
Copy link
Contributor

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;
}
Copy link
Contributor

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)

@cjcenizal
Copy link
Contributor Author

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.

@cjcenizal cjcenizal merged commit e570afc into elastic:master Feb 18, 2017
@cjcenizal cjcenizal deleted the improvement/ui-framework-docs-with-components branch February 18, 2017 00:13
cjcenizal added a commit that referenced this pull request Feb 18, 2017
…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
cjcenizal pushed a commit that referenced this pull request Feb 18, 2017
…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
@cjcenizal cjcenizal changed the title Migrate from a handrolled interface for building examples to using React Components directly. [UI Framework] Migrate from a handrolled interface for building examples to using React Components directly. Feb 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Team:Platform-Design Team Label for Kibana Design Team. Support the Analyze group of plugins. v5.4.0 v6.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants