跳至主要内容

[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 取得前一次的資料狀態

// 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;
}