Estruturando Componentes

@lucianomlima

@rodrigopr

Meetup React Salvador #5

Recapitulando...

Um componente pode ser definido de várias formas

Pode ser uma função

Functional Components

import React from 'react';

function Welcome(props) {
  return <h1>Hello {props.name}</h1>;
}

function Text(props) {
  return <div>{props.text}</div>;
}

function App() {
  return (
    <React.Fragment>
      <Welcome name="Johnny" />
      <Text text="Your App is Ready!" />
    </React.Fragment>
  );
}

Functional Components

import React from 'react';

const Welcome = props => (<h1>Hello {props.name}</h1>);
// const Welcome =({ name }) => (<h1>Hello {name}</h1>);

const Text = props => (<div>{props.text}</div>);
// const Text =({ text }) => (<div>{text}</div>);

const App = () => (
  <React.Fragment>
    <Welcome name="Johnny" />
    <Text text="Your App is ready!" />
  </React.Fragment>
);

( with arrow functions )

Pode ser uma classe

Class Components

import React from 'react';

const Welcome =({ name }) => (<h1>Hello {name}</h1>);

const Text =({ text }) => (<div>{text}</div>);

class App extends React.Component {
  componentDidMount() {
    doSomething();
  }

  render() {
    <React.Fragment>
      <Welcome name="Johnny" />
      <Text text="Your App is ready!" />
    </React.Fragment>    
  }
}

Stateless / Stateful

Stateless

import React from 'react';

const Avatar = ({ name, image }) => (<img src={image} alt="name" />);

const Welcome = props => (
  <div className="Welcome">
    <Avatar {...props} />
    <h1>Hello {props.name}</h1>
  </div>
);

// OR

const UserInfo = props => (
  <div className="UserInfo">
    <Avatar {...props} />
    <div className="UserInfo-name">
      <span>{props.name}</span>
    </div>
  </div>
);

Stateful

import React from 'react';
import MONTH_NAMES from './somewhere';

class Clock extends React.Component {
  state = {
    date: new Date()
  }

  getDate(date) {
    const month = MONTH_NAMES[date.getMonth()];
    return `${date.getDate()}/${month}/${date.getFullYear()}`;
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.getDate(this.state.date)}.</h2>
      </div>
    );
  }
}

Sua aplicação cresce...

É preciso pensar em:

Evitar duplicação de código

Compartilhar comportamentos

Pensar de forma escalável

Pensar no impacto na performance

Pensar na rastreabilidade de bugs

Controlled / Uncontrolled

Controlled vs Uncontrolled

Quem deve
controlar o state?

Uncontrolled Components

import React from 'react';

class Form extends React.Component {
  ...

  handleSubmitClick = (e) => {
    e.preventDefault();
    const name = this._name.value;
    myCustomSubmit(name);
  }

  render() {
    return (
      <form>
        <input type="text" ref={input => this._name = input} />
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </form>
    );
  }
}

Controlled Components

import React from 'react';

class Form extends React.Component {
  state = {
    name: ''
  }

  handleNameChange = (event) => {
    this.setState({ name: event.target.value });
  }

  handleSubmitClick = (e) => {
    e.preventDefault();
    myCustomSubmit(this.state.name);
  }

  render() {
    return (
      <form>
        <input type="text" value={this.state.name} onChange={this.handleNameChange} />
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </form>
    );
  }
}

Problem: Null Value

Controlled Components

Mais genéricos, maior reutilização

Componentes pais tem mais liberdade em como lidar com um componente filho

Uncontrolled Components

Mais simples, menos código envolvido

Ganha complexidade quando necessita suportar vários casos de uso

Não há bala de prata

class Toggle extends React.Component {
  state = {
    on: false
  }

  isControlled(prop) {
    return this.props[prop] !== undefined;
  }

  getState() {
    // Need to decide from where to get the value
    return {
      on: this.isControlled('on') ? this.props.on : this.state.on
    };
  }

  toggle = () => {
    if (this.isControlled("on")) {
      this.props.onToggle(!this.getState().on);
    } else {
      // After update state, call onToogle to update parent
      this.setState(
        ({ on }) => ({ on: !on }),
        () => {
          this.props.onToggle(this.getState().on);
        }
      );
    }
  }

  render() {
    return <Switch on={this.getState().on} onClick={this.toggle} />;
  }
}

Compound Components

Controlled /
Uncontrolled

JSX

Compound Components

Vários componentes trabalhando juntos

Conversando entre si

Interligados intimamente

Children prop


<MyComponent>Hello world!</MyComponent>

<div>Hello World</div>

<div>
  Hello World
</div>

<div>
  Hello
  World
</div>

<div>

  Hello World
</div>

( String Literals )

Children prop


<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

<MyList>
  Here is a list:
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</MyList>

render() {
  return [
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

( JSX Children )

Children prop


<MyComponent>{'foo'}</MyComponent>

const Item = (props) => (<li>{props.message}</li>);

const List = () => {
  const items = ['finish doc', 'submit pr', 'code review'];
  return (
    <ul>
      {items.map(message => <Item key={message} message={message} />)}
    </ul>
  );
}

render() {
  return [
    <li key="A">`${first} item`</li>,
    <li key="B">`${second} item`</li>,
    <li key="C">`${third} item`</li>,
  ];
}

( JavaScript Expressions )

Children prop

import React from 'react';

// Calls the children callback numTimes to produce a repeated component
const Repeat = (props) => {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

const ListOfTenThings = () => (
  <Repeat numTimes={10}>
    {(index) => (
      <div key={index}>
        This is item number {index} in the list
      </div>
    )}
  </Repeat>
);

export default ListOfTenThings;

( Functions as Children )

React.Children

Permite lidar com this.props.children

de diferentes formas

React.Children.map(children, function)

React.Children.forEach(children, function)

React.Children.count(children, function)

React.Children.only(children)

React.Children.toArray(children)

React.cloneElement

Retorna um novo React element

Novo elemento terá as props mescladas com as props do elemento original

Props key e ref serão preservadas

Make it flexible

Controlled /
Uncontrolled

Compound Components

High Order Components

High Order Component

"Dependency Injection" para componentes

Originado das High Order Functions

Isolamento de lógica para reaproveitamento

Isolamento de lógica para abstração

Isolamento de lógica para código mais limpo

Exemplo

HOC - Atenção

HOCs sempre retornam um novo componente!

Warnings

Não usar no render

Propriedades estática não são copiadas

Gera maior acoplamento de código

Pode tornar o debug mais difícil

Ref não é propagado automaticamente

HOC - Performance

import { connect } from '...';
import { compose, withState, ... } from '...';

const enhancer = compose(
  connect(...),
  withState(...),
  withSomething,
);

const EnhancedComponent = enhancer(Component);


/*
|-- connect render
   |-- withState render
      |-- withSomething render
         |-- Component render
*/

HOC - Testes

export const Component = (props) => {
  return (<div> ... </div>);
};

const enhancer = compose(
  ...
);

export default enhancer(Component);
import EnhancedComponent, { Component } from '../';

describe('Component', () => {
  // unit-test Component directly
});

describe('EnhancedComponent', () => {
  // you can also do integration test
});

HOC - Você já usa!

( talvez só não saiba... )

Relay.createContainer()

Redux: connect()

MobX: observer()

react-loadable

Controlled /
Uncontrolled

Compound Components

High Order Components

Render Props

Render Props

Mesmos propósitos que HOCs

Mais controle sobre onde é aplicado (render)

Não interferem em statics/refs

Exemplo

Nova buzzword,

React.createContext *

Você ainda vai ouvir muito!

Muitas libs dando suporte

Porém, não substitui HOC 100%

Quando não usar:

Só funciona no render? e Lifecycles?

Não ser dinâmico pode ter vantagens

Testes

Integração 👍🏽

Unitário: 👎🏽

Evite "render-props" hell

const Example = () => {
  return (
    <Theme>
      {theme => (
        <Counter>
          {counter => (
            <Toggle>
              {toggle => (
                <div style={{ color: theme === 'light' ? '#000' : '#fff' }}>
                  <span>{`Count: ${counter}`}</span>
                  <button onClick={toggle}>{'Toggle'}</button>
                </div>
              )}
            </Toggle>
          )}
        </Counter>
      )}
    </Theme>
  )
}

react-adopt

const Composed = adopt({
  theme: <Theme />,
  counter: <Counter />,
  toggle: <Toggle />,
})

const Example = () => (
  <Composed>
    {({ theme, counter, toggle }) => (
      <div style={{ color: theme === 'light' ? '#000' : '#fff' }}>
        <span>{`Count: ${counter}`}</span>
        <button onClick={toggle}>{'Toggle'}</button>
      </div>
    )}
  </Composed>
);

Qual usar?

HOC?

Render Props?

Compound Components?

Quem tal: nenhum?

Toda abstração tem um custo

Lei do menos poderoso

Tradução: Obrigado

Estruturando Componentes

By Luciano Lima

Estruturando Componentes

Meetup ReactSSA #5 - Composição de componentes voltada para maior reusabilidade

  • 994