Mapping a Class Component to a Functional Component with hooks
Sudhanshu Yadav
Front-end Architect at HackerRank
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
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
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
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
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
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
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
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
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
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 ❌
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 ✅
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
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
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
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
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
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
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 ❌
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 ✅