前提
React.jsとTypeScriptでプロフィールサイトを作っています。
useReducerを使用したカスタムフックを作成し、それを親コンポーネントで使用した際に、型のエラーが発生してしまいました。
エラーの解決方法をご教授いただきたいです。
実現したいこと
useReducerを使用したカスタムフックを呼び出した際に、型定義でエラーが出ているので解決したい。
実装内容
- github のリポジトリAPIを叩いて、取得するときの状態(画面の表示など)をuseReducerを使って変えたい。
- ロジックの再利用性を高めるため、カスタムフックを使って、JSXとReducerを使用している部分を分ける。
作成したファイルの説明
- constans.ts:状態を管理するファイル
- skillReducer.ts:Reducerを記載したファイル
- useSkills.ts:Reducerを使用、githubのリポジトリAPIを取得、取得したリポジトリ情報を配列で使用頻度の高い順番に並べる。
- Skills:useSkills関数を呼び出すファイル
発生している問題・エラーメッセージ
This expression is not callable. Not all constituents of type 'string | (() => LanguageState[] | undefined) | ((languageCount: number) => number)' are callable. Type 'string' has no call signatures.
該当のソースコード
constants.ts type DisplayStates = { idle: string; loading: string; success: string; error: string; initial?: string; }; export const requestStates: DisplayStates = { idle: "IDLE", loading: "LOADING", success: "SUCCESS", error: "ERROR", };
skillReducer.ts import { requestStates } from "../constants"; export type Action = { type: | "actionTypes.initial" | "actionTypes.fetch" | "actionTypes.success" | "actionTypes.error" | "languageList"; payload?: LanguageList; }; export type LanguageList = { languageList?: LanguageState[]; }; export type LanguageState = { language: string; count: number; }; export type ActionTypes = { initial: string; fetch: string; success: string; error: string; }; export const actionTypes: ActionTypes = { initial: "INITIAL", fetch: "FETCHING", success: "FETCH_SUCCESS", error: "FETCH_ERROR", }; export const initialState = { languageList: [], requestState: requestStates.idle, }; export const skillReducer = (state: LanguageList, action: Action) => { switch (action.type) { case "actionTypes.initial": { return { languageList: [], requestState: requestStates.initial, }; } case "actionTypes.fetch": { return { ...state, requestState: requestStates.loading, }; } case "actionTypes.success": { return { languageList: action.payload?.languageList, requestState: requestStates.success, }; } case "actionTypes.error": { return { languageList: [], requestState: requestStates.error, }; } default: { throw new Error(); } } };
useSkills.ts import { useEffect, useReducer, VFC } from "react"; import axios from "axios"; import { skillReducer, initialState, Action, LanguageState, } from "../reducers/skillReducer"; import { requestStates } from "../constants"; import { LanguageList } from "../reducers/skillReducer"; type Language = { state: string[]; dispatch?: string; language: string; }; const DEFAULT_MAX_PERCENTAGE = 100; const LANGUAGE_COUNT_BASE = 10; export const useSkills = () => { const [state, dispatch] = useReducer(skillReducer, initialState); const fetchApi = () => { axios .get<Language[]>("https://api.github.com/users/××××××××/repos") .then((response) => { const languageList: string[] = response.data.map((res) => res.language); const countedLanguageList = generateLanguageCountObj(languageList); dispatch({ type: "actionTypes.success", payload: { languageList: countedLanguageList }, }); }) .catch(() => { dispatch({ type: "actionTypes.error" }); }); }; useEffect(() => { if (state.requestState !== requestStates.loading) return () => { fetchApi(); }; }, [state.requestState]); useEffect(() => { dispatch({ type: "actionTypes.fetch" }); }, []); const generateLanguageCountObj = (allLanguageList: string[]) => { const notNullLanguageList = allLanguageList.filter( (language: string) => language != null ); const uniqueLanguageList = [...new Set(notNullLanguageList)]; return uniqueLanguageList.map((item) => { return { language: item, count: allLanguageList.filter((language) => language === item).length, }; }); }; const converseCountToPercentage = (languageCount: number): number => { if (languageCount > LANGUAGE_COUNT_BASE) { return DEFAULT_MAX_PERCENTAGE; } return languageCount * LANGUAGE_COUNT_BASE; }; const sortedLanguageList = () => state.languageList?.sort( (firstLang, nextLang) => nextLang.count - firstLang.count ); return [sortedLanguageList, state.requestState, converseCountToPercentage]; };
import { requestStates } from "../constants"; import Circle from "react-circle"; import { useSkills } from "../customHooks/useSkills"; export const Skills = () => { const [sortedLanguageList, fetchRequestState, converseCountToPercentage] = useSkills(); return ( <div id="skills"> <div className="container"> <div className="heading"> <h2>Skills</h2> </div> <div className="skills-container"> {fetchRequestState === requestStates.loading && ( <p className="description">取得中</p> )} {fetchRequestState === requestStates.success && sortedLanguageList().map((item, index) => ( <div className="skill-item" key={index}> <p className="description"> <strong>{item.language}</strong> </p> <Circle animate progress={converseCountToPercentage(item.count)} /> </div> ))} {fetchRequestState === requestStates.error && ( <p className="description">エラーが発生しました!</p> )} </div> </div> </div> ); };
試したこと
- useSkillsの戻り値に型を指定。
- エラーが出てる関数の戻り値に型を指定。
補足情報(FW/ツールのバージョンなど)
- "react": "^18.2.0",
- "typescript": "^4.8.4",
- "react-circle": "^1.1.1",
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2022/11/29 09:41
2022/11/29 09:44 編集
2022/12/02 23:37