[react] Snippets
Cancel Button with React Router
<!-- 使用 withRouter 後即有 history 可用 -->
<CancelButton htmlType="button" className="mr-10px" onClick={() => history.goBack()}>
Cancel
</CancelButton>
Split String Into Component
keywords: split
, parse
, react component
, string
根據正規式把字串轉換成 React Component:
const parseTime = (text) => {
const pattern = /\b(\d{2}:\d{2})\b/g;
const splitText = text.split(pattern);
if (splitText.length <= 1) {
return text;
}
const matches = text.match(pattern);
const newContent = splitText.reduce((arr, element) => {
if (!element) return arr;
if (matches.includes(element)) {
return [...arr, <Component />];
}
return [...arr, element];
}, []);
return newContent;
};
Spinner Component
The design is credits by Fran Pérez @ CodePen
import React from 'react';
import styled, { keyframes } from 'styled-components';
const offset = 187;
const duration = '1.4s';
const rotator = keyframes`
0% { transform: rotate(0deg); }
100% { transform: rotate(270deg); }
`;
const colors = keyframes`
0% { stroke: #4285F4; }
25% { stroke: #DE3E35; }
50% { stroke: #F7C223; }
75% { stroke: #1B9A59; }
100% { stroke: #4285F4; }
`;
const dash = keyframes`
0% { stroke-dashoffset: ${offset}; }
50% {
stroke-dashoffset: ${parseInt(offset) / 4};
transform:rotate(135deg);
}
100% {
stroke-dashoffset: ${offset};
transform:rotate(450deg);
}
`;
const StyledSVG = styled.svg`
animation: ${rotator} ${duration} linear infinite;
.path {
stroke-dasharray: ${offset};
stroke-dashoffset: 0;
transform-origin: center;
animation: ${dash} ${duration} ease-in-out infinite, ${colors} ${parseInt(duration) * 4}s ease-in-out
infinite;
}
`;
const Spinner = () => {
return (
<StyledSVG width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<circle
className="path"
fill="none"
strokeWidth="6"
strokeLinecap="round"
cx="33"
cy="33"
r="30"
></circle>
</StyledSVG>
);
};
export default Spinner;
isUnmounted 判斷
如果碰到這種問題,可以在 component 中多一個靜態的變數(不是會變動的 state),例 isUnmounted
:
const ReactComponent = () => {
const [data, setData] = useState();
let isUnmounted = false;
useEffect(() => {
const fetchData = async () => {
const data = await fetch('foobar');
// ⚠️ 問題可能出在這!
// 有可能 fetch 到 data 的時候,這個 Component 已經不存在了
// 因此要確認該 component 還沒 unmounted 的時候才能執行 setState
if (!isUnmounted) {
setData(data);
}
};
fetchData();
return () => {
isUnmounted = true;
};
}, []);
return <h1>react component</h1>;
};
也可以使用 useRef
:
使用
useRef
的差別主要在於,當這個 component 重新 render 的時候,可以取得上一次重新 render 的狀態,但在這裡每次都會把isUnmounted
設回false
,所以兩種方式應該都可以。
const ReactComponent = () => {
const [data, setData] = useState();
const isUnmounted = useRef(false);
useEffect(() => {
const fetchData = async () => {
const data = await fetch('foobar');
// ⚠️ 問題可能出在這!
// 有可能 fetch 到 data 的時候,這個 Component 已經不存在了
// 因此要確認該 component 還沒 unmounted 的時候才能執行 setState
if (!isUnmounted.current) {
setData(data);
}
};
fetchData();
return () => {
isUnmounted.current = true;
};
}, []);
return <h1>react component</h1>;
};
透過 useRef 取得前一次的資料狀態
- usePrevious @ useHooks
- How to get the previous props or state @ React > Hooks FAQ
// source: https://usehooks.com/usePrevious/
import { useState, useEffect, useRef } from 'react';
function App() {
const [count, setCount] = useState<number>(0);
// 取得前一次的 count 值
const prevCount: number = usePrevious<number>(count);
return (
<div>
<h1>
Now: {count}, before: {prevCount}
</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// Hook
function usePrevious<T>(value: T): T {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref: any = useRef<T>();
// Store current value in ref
useEffect(() => {
// 把新的值存到 ref 中
ref.current = value;
}, [value]); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}