Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 334 additions & 1 deletion ghdocs/BESTPRACTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,138 @@ interface IButton {
}
```

## Use @autobind. Avoid ALL binds in templates

The autobind decorator simplifies making event callbacks "bound" to the instance. It's easy to use:

```typescript
class Foo {

@autobind
public _onClick(ev: React.MouseEvent) {
}

}
```

When we use bind in a template, it means that the function is recreated every time, which is an anti-pattern.

## For gathering refs, avoid string refs, use resolve functions

Facebook has deprecated the use of string refs. Plus, string refs don't play very well with some scenarios like in Layered content, where the content may be asynchronously rendered. The recommendation is to use functions to resolve refs. These functions must be pre-bound to

Bad:
```typescript
public render() {
return <div ref='root' />
}
```

Bad:
```typescript
public render() {
return <div ref={ (el) => this._root = el } />
}
```

Good:
```typescript
private _root: HTMLElement;
private _resolveRoot: (element: HTMLElement) => any;

constructor() {
this._resolveRoot = (el: HTMLElement) => this._root = el;
}

public render() {
return <div ref={ this._resolveRoot } />
}
```

Note that it's very critical you do NOT inline the functions in render! This causes weird side effects, because the function
is recreated. If you do this, react will call your function with null to clear it, and then render, and then once complete, will
call it again with the element. This creates timing issues where your ref may be null when you didn't expect.

See example here that illustates refs being re-evaluated:

http://codepen.io/dzearing/pen/WGZOaR

## Use unique keys for items in a mapped array, avoid indexes for keys

```typescript
public render() {
return (
<div>
{ this.props.items.map((item, index)=> (
<div key={ item.key }>
<input type="text" />
</div>
)) }
</div>
);
}
```

When you need to map data, require that your data have unique ids available. When you map the data to content, use the id as
the item's key. This helps React's dom diffing to understand which elements represent which items.

When your code uses index as the key, this can cause unexpected side effects. For example, say that each component you map items to
maintains some internal state, like the value in an uncontrolled input box. Removing an item in the array will not reset that state
and react will think the first text box still represents key "0", which is actually now a different item, and thus will not re-render
the component, leaving the old state intact.

So lesson is, use actual unique ids for items that come from the data, rather than generated by the ux component, and thus require
that your data has ids so that you can use that as a unique identifier for rendering.

## Avoid nesting content in an array, wrap mapped content in component instead.

When we do inline mapped entries, we want to inline the content in the map:

Bad:
```typescript
class Foo extends React.Component {
public render() {
return (
<div>
{ items.map(item => (
<div key={ item.key } onClick={ this._onItemClick.bind(this, item) }>{ item.name }</div>
))}
</div>
);
}
}
```

Better:
```typescript
class Foo extends React.Component {
public render() {
return (
<div>
{ items.map(item => (
<ItemComponent key={ item.key } item={ item } />
)) }
</div>
);
}
}

class ItemComponent extends React.Component<...> {
let { item } = this.props;

render() {
return (
<div onClick={ this._onClick }>{ item.name }</div>
);
}

@autobind
_onClick(ev: MouseEvent) {

}
}
```

## Extend from BaseComponent instead of React.Component in most cases.

In the common folder, there exists a BaseComponent class. For simple components, it may be unnecessary to use.
Expand Down Expand Up @@ -56,7 +188,208 @@ If specific component elements need special classnames injected, add more classN

A component's SCSS file should ONLY include files applicable to the component, and should not define styles for any other component.

# Class name guidelines
# Data/state management best practices

## Things to flat out avoid

Singletons and globals. Do not share state of a component via a singleton instance object. This is a pure anti-pattern for the following reasons:

* Singletons have no lifetime and therefore can't initialize/clean up when they aren't used.
* Singletons often paint you into a corner when you want more than one of the same thing on the page.
* They are often difficult to test without polluting states required for other tests.
* The make non-browser scenario reuse really difficult. (Build tooling, server side code.)

Data and state should always follow the lifetime of the component that owns it. When that component is no longer needed, the state should
be garbage collected, event handlers should be removed, and xhr requires spawned in that context should be cancelled.

There are cases where everything should share a common instance of a store; Persona presence is a great example. We have solutions that enable the code within a component scope to access shared stores. See store usage section below.

## Use const values global to a file, rather than as static members

Don't use public/private statics, use file scope.

Bad:
```typescript
class Foo {
private static DEFAULT_DELAY = 300;
}
```

Good:
```typescript
const DEFAULT_DELAY = 300;

class Foo {
}
```

Note: using file scopes minimizes the name down to 1 character, whereas this.REALLY_LONG_NAME doesn't minify the name. Caveat being
that if you export it, minify won't touch exports.

## Use private members only for managing state local to a component that should not affect rendering when mutating

Bad:
```typescript
class Foo extends React.Component {

constructor(props) {
super(props);
this.state = {
isMounted: false
};
}

componentDidMount() {
this.setState({
isMounted: true
});
}

render() {
return <div>Hello!</div>
}
}
```

Good:
```typescript
class Foo extends React.Component {
private _isMounted: boolean;

constructor(props) {
super(props);
this._isMounted = false;
}

componentDidMount() {
this._isMounted = true;
}

render() {
return <div>Hello!</div>
}
}
```

Basically privates should only be use for tracking this that in no way affect the frequency of render. In some cases, you many
have an object which fires change events yet doesn't mutate, and you want to re-render on change events. In these cases, privates
may be used to store the object reference.

## Use component state only for managing local state that is private to a component that should trigger re-render on mutation

Bad:
```typescript
class Foo extends React.Component {
private _fooRocks: boolean;

render() {
return <div onClick={ this._onClick }>{ `Foo ${ this._fooRocks ? 'rocks' : 'rolls' }</div>;
}

private _onClick() {
this._fooRocks = !this._fooRocks;
this.forceUpdate();
}
```

Good:
```typescript
class Foo extends React.Component {
constructor() {
super(props);

this.state = {
fooRocks: false
};
}

render() {
return <div onClick={ this._onClick }>{ `Foo ${ this.state.fooRocks ? 'rocks' : 'rolls' }</div>;
}

private _onClick() {
this.setState({
fooRocks: !this.state.fooRocks
});
}
```

## Experimental: Use a store for storing shared state within a component heirachy which must be shared across objects

While React state management is very useful for simple, private state, it becomes unweildy when many things share that state
and you start ending up with many owners of the same state value. In these cases it may drive you towards a flux solution, but
before we jump there, lets call out a few things.

* Global singletons are code smell, do not use globals or singletons except for very rare cases
* Do not store complex shared state directly in the components, this is also code smell

Instead, make a store and wire dumb components together the store.

What is a store?

Think of a store as a self aware observable; it maintains state, and provides read/write access to that state. When its state
changes, it can emit change events causing components to re-evaluate.

Here's an example store which tracks selection:

```typescript
class SimpleSelectionStore extends BaseStore {
private _selectedKey: string;

public isSelected(key: string) {
return key === this._selectedKey;
}

public setSelected(key: string) {
if (this._selectedKey !== key) {
this._selectedKey = key;
this.emitChange();
}
}
}
```

While we do not want globals, we do want simplified access to the stores available in a given component scope.

You can use the StoreHost component to host stores within any given scope:

```typescript
const stores = {
selection: new SimpleSelectionStore()
};

public render() {
return (
<StoreHost stores={ stores }>
<UIContent />
</StoreHost>
);
}
```

You can write dumb components:

```typescript
const Item = (props) => (
<div onClick={ props.onClick }>{ `I am item ${ props.name } and I'm ${ props.isSelected ? 'selected' : 'unselected' }</div>
);
```

And you can use the `connect` function to create a ConnectedItem which wraps the dumb component and translates the props
and stores in its context into props for the dumb component:

```typescript
export ConnectedItem = connect(Item, (stores, props) => ({
name: props.item.name,
isSelected: stores.selection.isSelected(props.item.key),
onClick: () => stores.selection.setSelected(props.item.key)
}));
```

Now in this setup, your state is in one place, it is composable and can have lifetime, and your dumb ui has no understanding
of the contextual stores provided.

# CSS class name guidelines

TODO: include our class name guidelines.

Expand Down
1 change: 1 addition & 0 deletions src/Utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './common/BaseComponent';
export * from './utilities/css';
export * from './utilities/rtl';
export * from './utilities/hoist';
export * from './utilities/autobind';
Loading