React Patterns in Angular Land

- Hayden Braxton

 

Kent C. Dodds

Isaac Mann

Inspiration

Hayden Braxton

JSLovers

Why Patterns?

Why React Patterns?

Overview

Overview

- Composition vs Inheritance

- Compound Components

- Render Prop

- State Reducer

- Bonus!

Composition vs Inheritance

Compound Compomnents

const RadioOption = props => {
  return (
    <div onClick={props.onClick}>
      <RadioIcon isSelected={props.isSelected} />
      {props.children}
    </div>
  );
};
class RadioGroup extends Component {
  constructor(props) {
    super(props);
    this.state = {value: ""};
  }

  select = value => {
    this.setState({ value }, () => {
      this.props.onChange(this.state.value);
    });
  };

  render() {
    return (
      <Context.Provider
        value={{value: this.state.value, onSelect: this.select}}
      >
        {this.props.children}
      </Context.Provider>
    );
  }
}
render() {
  return (
    <RadioGroup onChange={this.onChange}>
      <Context.Consumer>
        {({ value, onSelect }) => (
          <RadioOption
            value="am"
            onClick={() => onSelect("am")}
            isSelected={value === "am"}
          >AM</RadioOption>
          <RadioOption
            value="fm"
            onClick={() => onSelect("fm")}
            isSelected={value === "fm"}
          >FM</RadioOption>
        )}
      </Context.Consumer>
    </RadioGroup>
  )
}

Render Prop

class Mouse extends React.Component {
  state = { x: 0, y: 0 }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return (
      <div
        style={{ height: '100%' }}
        onMouseMove={this.handleMouseMove}
      >
        {this.props.children(this.state)}
      </div>
    )
  }
}
const App = () => (
  <div style={{ height: '100%' }}>
    <Mouse>
     {({ x, y }) => (
       <h1>The mouse position is ({x}, {y})</h1>
     )}
    </Mouse>
  </div>
)
@Component({
  selector: 'mouse',
  template: `
  <div
    style="height: 100%"
    (mousemove)="handleMouseMove($event)"
  >
    <ng-container *ngTemplateOutlet="template; context: state">
    </ng-container>
  </div>
  `,
})
export class Mouse {
  @ContentChild(TemplateRef) template;
  state = { x: 0, y: 0 }

  handleMouseMove(event) {
    this.state = {
      x: event.clientX,
      y: event.clientY
    }
  }
}
@Component({
  selector: 'my-app',
  template: `
  <div style="height: 100%">
    <mouse>
      <ng-template let-x="x" let-y="y">
        // The template gives us the state we need
        // to render whatever we want here.
        <h1>The mouse position is ({{x}}, {{y}})</h1>
      </ng-template>
    </mouse>
  </div>
  `,
})
export class AppComponent {}

State Reducer

class Toggle extends React.Component {
  static defaultProps = {
    stateReducer: (state, changes) => changes,
  }
  internalSetState(changes, callback) {
    this.setState(state => {
      const changesObject =
        typeof changes === 'function' ? changes(state) : changes;
      const reducedChanges =
        this.props.stateReducer(state, changesObject) || {};
      const {type: ignoredType, ...onlyChanges} = reducedChanges;

      return Object.keys(onlyChanges).length ? onlyChanges : null;
    }, callback);
  }
  toggle = ({type = Toggle.stateChangeTypes.toggle} = {}) =>
    this.internalSetState(
      ({on}) => ({type, on: !on}),
      () => this.props.onToggle(this.state.on),
    )
  getTogglerProps = ({onClick, ...props} = {}) => ({
    onClick: callAll(onClick, () => this.toggle()),
    'aria-pressed': this.state.on,
    ...props,
  })
  getStateAndHelpers() {
    return {
      on: this.state.on,
      toggle: this.toggle,
    }
  }
  render() {
    return this.props.children(this.getStateAndHelpers())
  }
}
class Usage extends React.Component {
  state = {timesClicked: 0};
  handleToggle = (...args) => {
    this.setState(({timesClicked}) => ({
      timesClicked: timesClicked + 1,
    }))
  }
  handleReset = (...args) => {
    this.setState({timesClicked: 0})
    this.props.onReset(...args)
  }
  stateReducer = (state, changes) => {
    if (this.state.timesClicked >= 4) {
      return {...changes, on: false}
    }
    return changes
  }
  render() {
    const {timesClicked} = this.state; 
    return (
      <Toggle stateReducer={this.stateReducer} onToggle={this.handleToggle}>
        {({on, toggle}) => (
            <Switch on={on}/>
            {timesClicked > 4 ? (Whoa, you clicked too much!)
                : timesClicked > 0 ? (Click count: {timesClicked})
                : null}
        )}
      </Toggle>
    )
  }
}

Bonus!

Angular Context API

Angular Props Directive

Thanks!

Resources

React Patterns in Angular Land

By haydenbraxton

React Patterns in Angular Land

React and Angular are the most widely used front-end libraries today. Both tools have undoubtedly enabled developers to become more productive, and both have seen a lot of maturation in the past couple years. Rather than pitting the two against each other in a high-stakes death match, perhaps there exist opportunities for developers of one library to learn from the best practices of the other. In this talk, though perhaps not as exciting as a bloody cage fight, we’ll learn about the most popular patterns circulating in the React community and see how we can apply the same techniques to write better Angular code.

  • 700