Recoil은 Atom으로 부터 시작해서 Selector를 거쳐 React 컴포넌트까지 전달되는 하나의 Data-Flow Graph를 만들게 합니다
// src/state/QuizDifficulty.ts
import { atom } from 'recoil';
export default atom<string | undefined>({
key: 'QuizDifficulty',
default: undefined,
}); // 앱 전체에서 사용될 첫 페이지에서 선택한 난이도
// src/components/Organisms/QuizDifficulty.tsx
import { useRecoilState } from 'recoil';
import { QuizDifficultyState } from 'src/state';
const QuizDifficulty = () => {
const [quizDifficulty, setQuizDifficulty] =
useRecoilState(QuizDifficultyState);
const handleChange = (e: ChangeEvent<HTMLSelectElement>) => {
setQuizDifficulty(e.target.value);
};
return (
<select
data-testid={DIFFICULTY_SELECT_TEST_ID}
margin="16px 0px"
value={quizDifficulty}
onChange={handleChange}
>
{difficulties.map(difficulty => (
<option
key={difficulty}
value={difficulty == ANY_DIFFICULTY ? undefined : difficulty}
>
{difficulty == ANY_DIFFICULTY ? difficulty : difficulty.toUpperCase()}
</option>
))}
</select>
);
};
export default QuizDifficulty;
useRecoilState
라는 hook을 사용QuizDifficultyState
);// src/components/Organisms/LandingFooter.tsx
import { useResetRecoilState } from 'recoil';
import { InitialPropsState } from 'src/state';
useResetRecoilState(InitialPropsState);
// InitialPropsState.ts : selector
import { selector } from 'recoil';
export default selector<TResponseData>({
// atom이 아닌 selector로 선언된 global state
key: 'initialOrderState', // atom포함해서 unique한 key이어야 함
get: async ({ get }) => {
const queryData = get(QueryDataState); // atom으로 선언된 global state를 구독하고 있다가 변경되면 get: 에 할당된 async함수가 재 실행 됨
// QueryDataState가 변경 될 때 마다 서버로 부터 받아온 데이터(decodedResponseData)를 return
if (
queryData == undefined ||
window.location.pathname != `/${QUIZ_PAGENAME}`
)
return undefined;
const { amount, difficulty } = queryData;
const axios = customAxios();
const response = await axios({
method: 'GET',
params: {
amount,
difficulty,
type: 'multiple',
},
});
const decodedResponseData = {
...response.data,
results: response.data.results.map((quiz: TQuiz) => {
const decoded_correct_answer = decodeHtml(quiz.correct_answer);
const decoded_incorrect_answers = quiz.incorrect_answers.map(answer =>
decodeHtml(answer),
);
return {
...quiz,
question: decodeHtml(quiz.question),
correct_answer: decoded_correct_answer,
incorrect_answers: decoded_incorrect_answers,
examples: addCorrectAnswerRandomly(
decoded_incorrect_answers,
decoded_correct_answer,
),
};
}),
};
return decodedResponseData;
},
set: ({ get, set }) => {
const amount = get(QuizNumbersState); // atom state를 가져와서
const difficulty = get(QuizDifficultyState); // atom state를 가져와서
set(QueryDataState, { amount, difficulty }); // QueryDataState : atom state를 업데이트 해줌 -> get: 에서 QueryDataState를 구독하고 있으므로
// useResetRecoilState()로 set:을 호출해서 set으로 값을 업데이트 하면
// selector의 get: 에 할당된 async 함수가 실행 됨
set(QuizNumbersState, DEFAULT_NUMBERS);
set(QuizDifficultyState, undefined);
},
});
import { Suspense } from 'react';
import { Helmet } from 'react-helmet';
import { Route, Switch } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
import { QUIZ_PAGENAME, RESULT_PAGENAME } from 'src/constant';
import {
ErrorBoundary,
LandingPage,
QuizPage,
ResultsPage,
ShimmerPage,
} from 'src/components/Pages';
const Router = () => {
return (
<BrowserRouter>
<ErrorBoundary>
<Suspense fallback={<ShimmerPage />}>
<Switch>
<Route path={`/${QUIZ_PAGENAME}`}>
<Helmet title="Quiz page" />
<QuizPage />
</Route>
<Route path={`/${RESULT_PAGENAME}`}>
<Helmet title="Result page" />
<ResultsPage />
</Route>
<Route exact path="/">
<Helmet title="Landing page" />
<LandingPage />
</Route>
</Switch>
</Suspense>
</ErrorBoundary>
</BrowserRouter>
);
};
export default Router;
// https://opentdb.com/api.php?amount=1&difficulty=easy
{
"response_code": 0,
"results": [
{
"category": "Entertainment: Music",
"type": "multiple",
"difficulty": "easy",
"question": "Which Beatles album does NOT feature any of the band members on it's cover?",
"correct_answer": "The Beatles (White Album)",
"incorrect_answers": ["Rubber Soul", "Abbey Road", "Magical Mystery Tour"]
}
]
}