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

Docs: Include documentation on "Our Approach to Data" #1378

Merged
merged 3 commits into from
Feb 3, 2016

Conversation

aduth
Copy link
Contributor

@aduth aduth commented Dec 8, 2015

This pull request seeks to add a new document to our docs directory, with the purpose of outlining a history of our approaches to data, and to prescribe our current set of recommendations.

See Full Document

@rralian
Copy link
Contributor

rralian commented Dec 10, 2015

This is fantastic!... really well-written and considered. Thanks @aduth and others who helped talk this through. Our own contributors have been asking for this for a long time -- "It's hard to know which patterns to follow and which to avoid."

Question about this.

Use your best judgment when considering whether to add to the global state, but don't feel compelled to avoid React state altogether.

Is there a downside to tracking all state in the global state object? I suppose that would be pretty complex, considering you would need to track state against each individual component. I just get excited about the idea of these state objects helping us to debug problems in the application (by grabbing the object from an exception log or a help chat session). And that would be reduced somewhat by having some state that is not tracked globally... we couldn't exactly replicate what was going on in the browser.


__WIP__

- Can/should we use [JavaScript object getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) as part of the state tree in creating computed properties?
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't selectors make sense for retrieving computed properties?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wouldn't selectors make sense for retrieving computed properties?

I've intentionally left this section as WIP because I'm not really sold one way or the other yet. I also think this potentially leads us to other questions such as: Should we "fix" API responses to a format consistent with our naming guidelines (i.e. site.ID -> site.id).

The particular question that was posed was: Developers currently have easy access to a site domain by calling site.domain. The domain is a derived property based on other fields on the object. What recommendation should we have for access this data moving forward?

A few options I see are:

// 1)
// 
// Components should receive the most minimal data necessary, so pass `domain`
// using a selector which calculates the value based on the site from state tree

export default connect( ( state ) => {
    return {
        url: getSite( state, siteId ).URL,
        domain: getSiteDomain( state, siteId )
    };
} )( SiteLink );

// Pro: Component receives minimal props
// Con: Domain is not as accessible as calling to "site.domain"

// 2)
// 
// Components should receive the minimal set of props, so pass domain, but as
// a destructured property of the site object stored in state

export default connect( ( state, ownProps ) => {
    const { URL: url, domain } = getSite( state, ownProps.siteId );
    return { url, domain };
} )( SiteLink );

// Pro: As accessible as calling to "site.domain"
// Pro: Component receives minimal props
// Con: Either the site state is not normalized (has redundant computed data),
//      or we must define JavaScript getters in the state tree

// 3)
// 
// Pass the entire site object, including derived properties, to the component

export default connect( ( state, ownProps ) => {
    return { site: getSite( state, ownProps.siteId ) };
} )( SiteLink );

// Pro: Easy to access any necessary values from component itself
// Con: Not passing minimal set of props
// Con: Site not normalized or JavaScript getters in state tree

@aduth
Copy link
Contributor Author

aduth commented Dec 10, 2015

Is there a downside to tracking all state in the global state object?

I expect there to be some debate here 😄 It seemed to come down to weighing the benefits against the costs in terms of practicality. I'd agree with you that it'd be excellent for debugging if we could simply "export" the entire state of the application at any given moment. However, in practice, I think we'll find that it requires jumping through hoops to make it work, with particular concerns around:

  • Where does the interface state live in the tree?
    • At the root ui key? Will that scale?
    • Aligned to components or to routes? What if a component is reused in different contexts?
  • When does the interface state get cleared?
    • In creating a reducer for this state, am I responsible for clearing it in response to a route change?

It's also a significant amount of code to do well - implementing and testing a reducer, actions, action types, etc, all for a simple toggle on a component? Why go to such great lengths to avoid using a feature of React that's afforded to us for this very purpose - to maintain the state of a particular piece of the render hierarchy for the duration of the time that it's mounted?

Playing devil's advocate to myself:

  • I foresee a possible future where React only includes stateless function components, and has no concept of state built-in.
  • It may be wiser for us to be consistent with how we store data, UI or otherwise.


## Current Recommendations

All new data requirements should be implemented as part of the global Redux state tree. The `client/state` directory contains all of the behavior describing the global application state. The folder structure of the `state` directory should directly mirror the sub-trees within the global state tree. Each sub-tree can include their own reducer, actions, and selectors.
Copy link
Contributor

Choose a reason for hiding this comment

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

The client/state directory contains all of the behavior describing the global application state.

Shouldn't this be shared/state, so we can use it server-side?

Copy link
Member

Choose a reason for hiding this comment

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

I really want to avoid more fragmentation of /shared | /client. Can we figure out a plan for it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure about the implications for node and webpack -- @ehg and @seear will know better.

Other than that, I guess we'll still need some way to preserve components as usable-on-server though, so they aren't accidentally broken e.g. by non-team committers. We might want to turn some of https://github.com/Automattic/wp-calypso/blob/master/shared/README.md into general guidelines that are applicable to client-only components, and a subset for components that are meant to be used on the server side, too.

Writing tests to ensure components are still working server-side is going to be quite tedious :-/

CC @ehg @seear @mcsf

@aduth aduth added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] In Progress labels Jan 12, 2016
@aduth aduth force-pushed the add/docs-data-approach branch from 258b0a5 to aff948e Compare January 12, 2016 14:58
@apeatling
Copy link
Member

@nb What do you think about this? Think we can we merge this doc?

@aduth
Copy link
Contributor Author

aduth commented Jan 20, 2016

As evidenced by recent commits, some of the more specific guidelines are still unsettled, subject to changes through experimentation. I'd like to have at least some form of this document merged to master in the near future, so perhaps we decide to either (a) remove specific guidelines or (b) be content that they're in flux, and future revisions can be made through subsequent pull requests.

@apeatling
Copy link
Member

👍

function PostDeleteButton( { label, delete } ) {
return (
<button onClick={ delete }>
{ this.translate( 'Delete "%s"', { args: [ label ] } ) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this.translate() available to stateless function components? I didn't think it was.

Copy link
Member

Choose a reason for hiding this comment

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

It's not.

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 this.translate() available to stateless function components? I didn't think it was.

You're correct, it's not available. Updated to use React.Component class instead in e6611aa.

@rralian
Copy link
Contributor

rralian commented Jan 27, 2016

I'd like to have at least some form of this document merged to master in the near future, so perhaps we decide to either (a) remove specific guidelines or (b) be content that they're in flux, and future revisions can be made through subsequent pull requests.

I vote b. Let's merge this. I had one concern that I don't think you can use the translate mixin from a stateless function, so (unless I'm wrong) it'd be nice to change that to use React.createClass. But otherwise I think we should merge this. 👍

@rralian rralian added [Status] Ready to Merge and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Jan 27, 2016
}
```

### Container Components
Copy link
Member

Choose a reason for hiding this comment

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

I think we should revise this with the latest addition of Query components.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should revise this with the latest addition of Query components.

Anything specifically that you're finding to be inaccurate? There were revisions in 6cc3c69 to reflect that we should not be using the existence of data to determine whether fetching should occur.

Copy link
Member

Choose a reason for hiding this comment

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

I don't consider Query components Container Components in the way we have fetching components wrapping UI/app components and I think it would be a bit confusing to have so many concepts around them.

@aduth aduth force-pushed the add/docs-data-approach branch from 87edd36 to 5d989b3 Compare February 3, 2016 14:13
@aduth
Copy link
Contributor Author

aduth commented Feb 3, 2016

@mtias : Pushed dc3cec5 with some revised terminology with the goal of removing any distinctions between "app components" and "connected components", or between "fetching components" and "query components". Since you're correct that query components aren't really a container component, I've renamed the major section to "Data Components".

aduth added a commit that referenced this pull request Feb 3, 2016
Docs: Include documentation on "Our Approach to Data"
@aduth aduth merged commit 279c2e8 into master Feb 3, 2016
@aduth aduth deleted the add/docs-data-approach branch February 3, 2016 15:19
@mtias
Copy link
Member

mtias commented Feb 3, 2016

💥

@aduth aduth added the State label Jul 28, 2016
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.

7 participants