React Design Patterns - Static Components

May 12, 2019

This Component pattern is perhaps the most intuitive react design pattern for developers who're used to React. However, you might find it interesting that with ES6, you could use static components inside your component that share the state with your component.

Take for example, the Dropdown.js component:

A Simple Component: Dropdown.js

import React, { Component } from "react";
/**
 * props:
 *    onToggle(state object) func - A callback after the component is toggled.
 *    label string - The label for the Dropdown
 *    children object - The content that needs to be shown or hidden
 */
export default class Dropdown extends Component {
  state = {
    show: false
  };

  toggle = () => {
    const { onToggle = () => {} } = this.props;
    this.setState(
      ({ show }) => {
        return {
          show: !show
        };
      },
      () => onToggle(this.state)
    );
  };

  render() {
    const { label = "Show", children } = this.props;
    const { show } = this.state;
    return (
      <div className="dropdown">
        <label onClick={this.toggle}>{label}</label>
        {show ? <div className="content">{children}</div> : null}
      </div>
    );
  }
}

Usage: App.js

import React from "react";
import { Dropdown } from "./Dropdown";

function App() {
  return (
    <div style={{ width: 640, margin: "15px auto" }}>
      <Dropdown>Content</Dropdown>
    </div>
  );
}

render(<App />, document.getElementById("root"));

Here, we're rendering the children as is. Instead of just the string Content, we could have rendered a component whose state would be entirely controlled by App.js.


What if you wanted to customize your dropdown? Say for example, you want to render something even if the dropdown is closed. Or have some complex HTML instead of label and so on? What react design pattern should you follow?

The following is kind of an upgrade with static components along with React's Context API. We needed to use the Context API to allow rendering of our component even inside nested HTML (as used in App.js in the example below).

Simple Component with Context API and Static Components: Dropdown.js

import React, { Component } from "react";

const DropdownContext = React.createContext({
  show: false,
  toggle: () => {}
});

export default class Dropdown extends Component {
  static Label = ({ children = "Show", toggle }) => (
    <DropdownContext.Consumer>
      {({ toggle }) => <span onClick={toggle}>{children}</span>}
    </DropdownContext.Consumer>
  );
  static Hidden = ({ show, children }) => (
    <DropdownContext.Consumer>{({ show }) => (show ? null : children)}</DropdownContext.Consumer>
  );
  static Content = ({ show, children }) => (
    <DropdownContext.Consumer>{({ show }) => (show ? children : null)}</DropdownContext.Consumer>
  );
  toggle = () => {
    const { onToggle = () => {} } = this.props;
    this.setState(
      ({ show }) => {
        return {
          show: !show
        };
      },
      () => onToggle(this.state.show)
    );
  };
  state = {
    toggle: this.toggle,
    show: false
  };

  render() {
    return (
      <DropdownContext.Provider value={this.state}>{this.props.children}</DropdownContext.Provider>
    );
  }
}

The Dropddown.Label, Dropdown.Content and Dropdown.Hidden are static components which have access to the state of the component.

Usage: App.js

import React from "react";
import { Dropdown } from "./Dropdown";

function App() {
  return (
    <div className="App">
      <Dropdown>
        <Dropdown.Label>Click here to open dropdown</Dropdown.Label>
        // Note the nested content
        <div className="content-container">
          <Dropdown.Content>This is the content</Dropdown.Content>
        </div>
      </Dropdown>
    </div>
  );
}

export default App;

However, this react design pattern, though easy to use, does not give us the flexibility we look for in a component. We can obtain that through the use of another react pattern called Render Props.

Donate