質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
86.02%
TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

[React.js/Typescript]カスタムフックを使ってuseReducerを使用し、そのカスタムフックを呼び出した際にエラーがでた。

fje
fje

総合スコア1

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

1回答

0グッド

0クリップ

220閲覧

投稿2022/11/28 22:29

前提

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",

以下のような質問にはグッドを送りましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

グッドが多くついた質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

下記のような質問は推奨されていません。

  • 間違っている
  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

回答1

1

ベストアンサー

特に型を指定せずに[sortedLanguageList, state.requestState, converseCountToPercentage]のような配列リテラルを作ると、それは「入っているもののUnionが入りうる」という扱いとなります。

[state, dispatch]のような、要素ごとに違う型が割り当てられたタプルを作りたい場合、[string, (value: string) => void]のような型指定を(asで入れる、あるいは関数の返り値の型として宣言するなどで)行わなければなりません。

投稿2022/11/28 23:44

maisumakun

総合スコア141550

fje❤️を押しています

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

fje

2022/11/29 09:41

ご回答ありがとうございます! ご確認したい点があるのですが、修正すべき点は2点で相違ないでしょうか? 修正1) 下記の3つファイルの型を指定する。 sortedLanguageList state.requestState, converseCountToPercentage 修正2) [state, dispatch]を定義している、useReducer(skillReducer, initialState);に[string, (value: string) => void]のような型指定をする。
maisumakun

2022/11/29 09:44 編集

どちらも不要です。それらには自動的に適切な型が付与されています。 質問本文に書いた[sortedLanguageList, state.requestState, converseCountToPercentage]というリテラル(あるいはuseSkillsの返り値)に型を当ててください。
fje

2022/12/02 23:37

エラーが出ている関数の型を作成し、useSkillsの返り値に当てたところ、直りました! とても助かりました!🙇‍♂️ ありがとうございます! 追加したコード↓ export type UseSkills = [ sortedLanguageList: () => LanguageState[] | undefined, fetchRequestState: string | undefined, converseCountToPercentage: (languageCount: number) => number ]; export function useSkills(): UseSkills { .... }

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
86.02%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。