FunctionalComp = ClassComp.map(React.hooks);

Mapping a Class Component to a Functional Component with hooks

Sudhanshu Yadav

Front-end Architect at HackerRank

Lifecycle of a Class Component (mount)

Lifecycle of a Class Component (update)

State -> useState()

export class ClassStateExample extends Component {
  state = {
    count: 0
  };
  render() {
    const { count } = this.state;
    return (
      <div>
        <p>Current Count: {count}</p>
        <button onClick={() => this.setState({ count: count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}
export function FunctionStateExample() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Class Component

Functional Component

State -> useReducer()

export class ClassStateExample extends Component {
  state = {
    book: {
      title: "Sapiens",
      author: "Yuval Noah Harari"
    }
  };
  updateBookTitle(title) {
    const { book } = this.state;
    this.setState({
      book: {
        ...book,
        title: title
      }
    });
  }
  render() {
    const {
      book: { author, title }
    } = this.state;
    return (
      <div>
        <p>Book: {`${title} by ${author}`}</p>
        <button onClick={() => this.updateBookTitle("Homo Deus")}>
          Update book Title
        </button>
      </div>
    );
  }
}

Class Component

State -> useReducer()

function bookReducer(state, action) {
  if (action.type === "UPDATE_BOOK_TITLE") {
    return {
      ...state,
      title: action.title
    };
  }

  return state;
}

export function FunctionStateExample() {
  const [book, updateBook] = useReducer(bookReducer, {
    title: "Sapiens",
    author: "Yuval Noah Harari"
  });

  const { title, author } = book;

  return (
    <div>
      <p>Book: {`${title} by ${author}`}</p>
      <button
        onClick={() => {
          updateBook({
            type: "UPDATE_BOOK_TITLE",
            title: "Homo Deus"
          });
        }}
      >
        Update book Title
      </button>
    </div>
  );
}

Functional Component

Constructor

function getInstanceId(type) {
  return Math.random();
}

export class ClassConstructorExample extends Component {
  constructor(props) {
    super();
    const { start = 0 } = props;

    this.state = {
      count: start
    };

    this.instanceId = getInstanceId("Class");
  }
  render() {
    const { count } = this.state;

    return (
      <div>
        <p>Current Count: {count}</p>
        <button onClick={() => this.setState({ count: count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

Class Component

Constructor

function getInstanceId(type) {
  return Math.random();
}

function useOnce(cb) {
  const ref = useRef({});
  if (!ref.current.called) {
    ref.current.value = cb();
    ref.current.called = true;
  }
  return ref.current.value;
}

export function FunctionConstructorExample(props) {
  const { start = 0 } = props;
  const [count, setCount] = useState(start);

  const instanceId = useOnce(() => {
    return getInstanceId("Functional");
  });

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Functional Component

getDerivedStateFromProps

class ClassAuthExample extends React.Component {
  state = {
    tab: "login"
  };
  static getDerivedStateFromProps(props, state) {
    if (state.tab === "signup" && props.signupDisabled === true) {
      return {
        tab: "login"
      };
    }

    return null;
  }
  changeTab = tab => {
    this.setState({ tab });
  };
  render() {
    const { tab } = this.state;
    return (
      <div className="auth">
        <ul className="tabs">
          <li className="tab" onClick={() => this.changeTab("login")}>
            Login
          </li>
          <li className="tab" onClick={() => this.changeTab("signup")}>
            Signup
          </li>
        </ul>
        <div className="form-container">
          <div className="placeholder">{tab} form</div>
        </div>
      </div>
    );
  }
}

Class Component

getDerivedStateFromProps

function FunctionAuthExample(props) {
  const [tab, changeTab] = useState("login");

  if (tab === "signup" && props.signupDisabled === true) {
    changeTab("signup");
  }

  return (
    <div className="auth">
      <ul className="tabs">
        <li className="tab" onClick={() => changeTab("login")}>
          Login
        </li>
        <li className="tab" onClick={() => changeTab("signup")}>
          Signup
        </li>
      </ul>
      <div className="form-container">
        <div className="placeholder">{tab} form</div>
      </div>
    </div>
  );
}

Functional Component

componentDidMount

class ClassComponent extends React.Component {
  componentDidMount() {
    const { start } = this.props;
    logCount(start);
  }
  render() {
    const { start } = this.props;
    return <div>{start}<div>
  }
}
function FunctionalComponent(props) {
  const { start } = props;

  useEffect(() => {
    logCount(start);
  }, []);

  return <div>{start}</div>;
}

Class Component

Functional Component

componentDidMount (Async Handler)

export class ClassMountExample extends Component {
  state = {
    count: 0
  };
  componentDidMount() {
    doSomethingAsync().then(() => {
      console.log(`ClassComponent's state: ${this.state.count}`);
    });
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        <p>Current Count: {count}</p>
        <button onClick={() => this.setState({ count: count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

Class Component

componentDidMount (Async Handler)

export function FunctionMountExample(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    doSomethingAsync().then(() => {
      console.log(`FunctionalComponent's state: ${count}`);
    });
  }, []);

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Functional Component

componentDidMount (Async Handler)

function useCurrentCallback(cb) {
  const ref = useRef();
  ref.current = cb;
  return function(...args) {
    return ref.current(...args);
  };
}

export function FunctionMountExample(props) {
  const [count, setCount] = useState(0);

  const handle = useCurrentCallback(() => {
    console.log(`ClassComponent's state: ${count}`);
  });

  useEffect(() => {
    doSomethingAsync().then(handle);
  }, []);

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Functional Component

shouldComponentUpdate

export class ClassShouldUpdateExample extends React.Component {
  state = {
    count: 0,
    trackerCount: 0
  };
  shouldComponentUpdate(nextProps, nextState) {
    return nextState.count !== this.state.count;
  }
  render() {
    const { count, trackerCount } = this.state;
  
    return (
      <div>
        <p>Current Count: {count}</p>
        <button onClick={() => this.setState({ count: count + 1 })}>
          Increment
        </button>
        <button onClick={() => this.setState({ trackerCount: count + 1 })}>
          Track
        </button>
      </div>
    );
  }
}

Class Component 

shouldComponentUpdate

export function FunctionShouldUpdateExample() {
  const [count, setCount] = useState(0);
  const [trackerCount, setTrackerCount] = useState(0);

  return useMemo(
    () => {
      return (
        <div>
          <p>Current Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
          <button
            onClick={() =>
              setTrackerCount(prevTrackerCount => prevTrackerCount + 1)
            }
          >
            Track
          </button>
        </div>
      );
    },
    [count]
  );
}

Functional Component 

shouldComponentUpdate (Check for only props)

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default memo(MyComponent, areEqual);

Functional Component 

componentDidUpdate

export class ClassAnimateExample extends Component {
  state = {};
  wrapper = createRef();
  componentDidUpdate(prevProps) {
    const { point: prevPoint } = prevProps;
    const { point } = this.props;

    if (prevPoint < point) {
      this.moveTo("right");
    } else if (prevPoint > point) {
      this.moveTo("left");
    }
  }
  moveTo(direction) {
    this.setState({ direction });
    //do something to animate element to provide direction
    //animate(this.wrapper.current, direction);
  }
  render() {
    const { direction } = this.state;
    return <div ref={this.wrapper}>{`Direction: ${direction}`}</div>;
  }
}

Class Component 

componentDidUpdate

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  });

  return ref.current;
}

export function FunctionAnimateExample(props) {
  const [direction, setDirection] = useState(0);
  const wrapper = useRef();

  const prevProps = usePrevious(props);

  useEffect(() => {
    if (prevProps && prevProps.point !== props.point) {
      const direction = prevProps.point < props.point ? "right" : "left";
      setDirection(direction);
      //do something to animate element to provide direction
      //animate(wrapper.current, direction);
    }
  });

  return <div ref={wrapper}>{`Direction: ${direction}`}</div>;
}

Functional Component 

componentWillUnmount

export class ClassWillUnmountExample extends Component {
  state = {
    count: 0
  };
  componentWillUnmount() {
    doSomething();
    console.log(`ClassComponent's state: ${this.state.count}`);
  }
  render() {
    const { count } = this.state;
    return (
      <div>
        <p>Current Count: {count}</p>
        <button onClick={() => this.setState({ count: count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

Class Component 

componentWillUnmount

export function FunctionWillUnmountExample(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    return () => {
      doSomething();
    };
  }, []);

  useEffect(() => {
    return () => {
      console.log(`FunctionalComponent's state: ${count}`);
    };
  }, []);

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Functional Component

componentWillUnmount

function useCurrentCallback(cb) {
  const ref = useRef(cb);
  ref.current = cb;
  return function(...args) {
    return ref.current(...args);
  };
}

export function FunctionWillUnmountExample(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    return () => {
      doSomething("Functional");
    };
  }, []);

  const unmountHandler = useCurrentCallback(() => {
    console.log(`FunctionalComponent's state: ${count}`);
  });

  useEffect(() => {
    return unmountHandler;
  }, []);

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Functional Component

Code Sandbox Link

Github repo Link

Thank You...

Map class to functions on react

By Sudhanshu Yadav

Map class to functions on react

  • 713