Skip to content

Commit f5844a8

Browse files
authored
refactor(Cube): rewrite class component to functional (#8170)
1 parent 0093c61 commit f5844a8

File tree

1 file changed

+100
-145
lines changed

1 file changed

+100
-145
lines changed

src/components/Cube/Cube.jsx

Lines changed: 100 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,69 @@
11
// Import External Dependencies
22
import PropTypes from "prop-types";
3-
import { Component } from "react";
4-
5-
export default class Cube extends Component {
6-
static propTypes = {
7-
hover: PropTypes.bool,
8-
theme: PropTypes.string,
9-
depth: PropTypes.number,
10-
repeatDelay: PropTypes.number,
11-
className: PropTypes.string,
12-
continuous: PropTypes.bool,
13-
};
14-
15-
static defaultProps = {
16-
hover: false,
17-
theme: "dark",
18-
depth: 30,
19-
repeatDelay: 1000,
20-
};
3+
import { useEffect, useRef, useState } from "react";
214

22-
state = {
23-
x: 0,
24-
y: 0,
25-
z: 0,
26-
iteration: 0,
27-
};
5+
export default function Cube({
6+
hover = false,
7+
theme = "dark",
8+
depth = 30,
9+
repeatDelay = 1000,
10+
className = "",
11+
continuous,
12+
}) {
13+
const [state, setState] = useState({ x: 0, y: 0, z: 0, iteration: 0 });
14+
const containerRef = useRef(null);
15+
const timeoutRef = useRef(null);
2816

29-
render() {
30-
const { x, y, z } = this.state;
31-
const { theme, depth, className = "" } = this.props;
17+
const { x, y, z, iteration } = state;
3218

33-
return (
34-
<div
35-
className={`cube__container ${className}`}
36-
style={{
37-
width: `${depth * 1.5}px`,
38-
height: `${depth * 1.5}px`,
39-
paddingLeft: `${depth / 1.7}px`,
40-
}}
41-
>
42-
<span
43-
ref={(ref) => (this.container = ref)}
44-
className={`cube cube--${theme} relative block [transform-style:preserve-3d]`}
45-
style={{
46-
width: `${depth}px`,
47-
paddingBottom: `${depth * 0.5}px`,
48-
transform: "rotateX(-35.5deg) rotateY(45deg)",
49-
}}
50-
>
51-
<figure
52-
className="cube__outer inline-block [transform-style:preserve-3d] transition-transform duration-1000"
53-
style={{
54-
width: `${depth}px`,
55-
height: `${depth}px`,
56-
transform: `translateX(-50%)
57-
scale3d(1,1,1)
58-
rotateX(${x}deg)
59-
rotateY(${y}deg)
60-
rotateZ(${z}deg)`,
61-
}}
62-
>
63-
{this._getFaces("outer")}
64-
</figure>
65-
<figure
66-
className="cube__inner absolute -top-[2px] left-0 inline-block [transform-style:preserve-3d] transition-transform duration-1000"
67-
style={{
68-
width: `${depth}px`,
69-
height: `${depth}px`,
70-
transform: `translateX(-50%) translateY(2px)
71-
scale3d(0.5,0.5,0.5)
72-
rotateX(${-x}deg)
73-
rotateY(${-y}deg)
74-
rotateZ(${-z}deg)`,
75-
}}
76-
>
77-
{this._getFaces("inner")}
78-
</figure>
79-
</span>
80-
</div>
81-
);
82-
}
83-
84-
componentDidMount() {
85-
const { hover, continuous, repeatDelay } = this.props;
19+
useEffect(() => {
20+
const container = containerRef.current;
8621

8722
if (hover) {
88-
this.container.addEventListener("mouseenter", this._spin);
89-
this.container.addEventListener("mouseleave", this._reset);
23+
const spin = () => {
24+
const axes = ["x", "y", "z", "iteration"];
25+
const axis = axes[Math.floor(Math.random() * axes.length)];
26+
const sign = Math.random() < 0.5 ? -1 : 1;
27+
setState((prev) => ({ ...prev, [axis]: sign * 90 }));
28+
};
29+
30+
const reset = () => {
31+
setState((prev) => ({ ...prev, x: 0, y: 0, z: 0 }));
32+
};
33+
34+
container.addEventListener("mouseenter", spin);
35+
container.addEventListener("mouseleave", reset);
36+
37+
return () => {
38+
container.removeEventListener("mouseenter", spin);
39+
container.removeEventListener("mouseleave", reset);
40+
};
9041
} else if (continuous) {
9142
let degrees = 0;
9243
const axis = "y";
9344

9445
const animation = () => {
9546
const obj = {};
9647
obj[axis] = degrees += 90;
97-
this.setState({
48+
setState((prev) => ({
49+
...prev,
9850
...obj,
99-
iteration: (this.state.iteration + 1) % 4,
100-
});
51+
iteration: (prev.iteration + 1) % 4,
52+
}));
10153
// eslint-disable-next-line no-use-before-define
10254
tick();
10355
};
10456

10557
const tick = () =>
10658
setTimeout(() => requestAnimationFrame(animation), repeatDelay);
10759

108-
this._timeout = tick();
109-
}
110-
}
111-
112-
componentWillUnmount() {
113-
const { hover, continuous } = this.props;
60+
timeoutRef.current = tick();
11461

115-
if (hover) {
116-
this.container.removeEventListener("mouseenter", this._spin);
117-
this.container.removeEventListener("mouseleave", this._reset);
118-
} else if (continuous) {
119-
clearTimeout(this._timeout);
62+
return () => clearTimeout(timeoutRef.current);
12063
}
121-
}
122-
123-
/**
124-
* Get all faces for a cube
125-
*
126-
* @param {'inner' | 'outer' } type
127-
* @return {array} - An array of nodes
128-
*/
129-
_getFaces(type) {
130-
const { iteration } = this.state;
64+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
13165

66+
const getFaces = (type) => {
13267
// Keep the thicker border on
13368
// the outside on each iteration
13469
const borderWidthMap = {
@@ -201,50 +136,70 @@ export default class Cube extends Component {
201136
key={i}
202137
className={`cube__face ${baseFaceClasses} ${variantClasses}`}
203138
style={{
204-
transform: `${rotation} translateZ(${this.props.depth / 2}px)`,
139+
transform: `${rotation} translateZ(${depth / 2}px)`,
205140
...borderStyles,
206141
}}
207142
/>
208143
);
209144
});
210-
}
211-
212-
/**
213-
* Get a random axis
214-
*
215-
* @return {string} - A random axis (i.e. x, y, or z)
216-
*/
217-
_getRandomAxis() {
218-
const axes = Object.keys(this.state);
219-
220-
return axes[Math.floor(Math.random() * axes.length)];
221-
}
222-
223-
/**
224-
* Spin the cubes in opposite directions semi-randomly
225-
*
226-
* @param {object} e - Native event
227-
*/
228-
_spin = () => {
229-
const obj = {};
230-
const axis = this._getRandomAxis();
231-
const sign = Math.random() < 0.5 ? -1 : 1;
232-
233-
obj[axis] = sign * 90;
234-
235-
this.setState(obj);
236145
};
237146

238-
/**
239-
* Rotate the cubes back to their original position
240-
*
241-
* @param {object} e - Native event
242-
*/
243-
_reset = () => {
244-
this.setState({
245-
x: 0,
246-
y: 0,
247-
z: 0,
248-
});
249-
};
147+
return (
148+
<div
149+
className={`cube__container ${className}`}
150+
style={{
151+
width: `${depth * 1.5}px`,
152+
height: `${depth * 1.5}px`,
153+
paddingLeft: `${depth / 1.7}px`,
154+
}}
155+
>
156+
<span
157+
ref={containerRef}
158+
className={`cube cube--${theme} relative block [transform-style:preserve-3d]`}
159+
style={{
160+
width: `${depth}px`,
161+
paddingBottom: `${depth * 0.5}px`,
162+
transform: "rotateX(-35.5deg) rotateY(45deg)",
163+
}}
164+
>
165+
<figure
166+
className="cube__outer inline-block [transform-style:preserve-3d] transition-transform duration-1000"
167+
style={{
168+
width: `${depth}px`,
169+
height: `${depth}px`,
170+
transform: `translateX(-50%)
171+
scale3d(1,1,1)
172+
rotateX(${x}deg)
173+
rotateY(${y}deg)
174+
rotateZ(${z}deg)`,
175+
}}
176+
>
177+
{getFaces("outer")}
178+
</figure>
179+
<figure
180+
className="cube__inner absolute -top-[2px] left-0 inline-block [transform-style:preserve-3d] transition-transform duration-1000"
181+
style={{
182+
width: `${depth}px`,
183+
height: `${depth}px`,
184+
transform: `translateX(-50%) translateY(2px)
185+
scale3d(0.5,0.5,0.5)
186+
rotateX(${-x}deg)
187+
rotateY(${-y}deg)
188+
rotateZ(${-z}deg)`,
189+
}}
190+
>
191+
{getFaces("inner")}
192+
</figure>
193+
</span>
194+
</div>
195+
);
250196
}
197+
198+
Cube.propTypes = {
199+
hover: PropTypes.bool,
200+
theme: PropTypes.string,
201+
depth: PropTypes.number,
202+
repeatDelay: PropTypes.number,
203+
className: PropTypes.string,
204+
continuous: PropTypes.bool,
205+
};

0 commit comments

Comments
 (0)