'Generic type in useReducer for a returned parameter
I am writing a custom hook to fetch some data from an API. I would like the returned data to be type-safe if possible. Can this be done with generics?
type Action = { type: 'PENDING' } | { type: 'SUCCESS'; payload: any } | { type: 'FAIL' };
interface State {
isLoading: boolean;
isError: boolean;
data: any;
}
const dataFetchReducer = (state: State, action: Action): State => {
switch (action.type) {
case 'PENDING':
return {
...state,
isLoading: true,
};
case 'SUCCESS': {
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
}
case 'FAIL':
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error('Action not supported');
}
};
const baseUrl = 'http://localhost:4000';
function useDataFetchFromAPI(initUrl: string, initData: any) {
const [url, setUrl] = useState(`${baseUrl}${initUrl}`);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initData,
});
// effect only runs if url changes
// see deps array (2nd argument)
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'PENDING' });
try {
const result = await axios(url);
dispatch({ type: 'SUCCESS', payload: result.data });
} catch (error) {
dispatch({ type: 'FAIL' });
}
};
fetchData();
}, [url]);
const executeFetch = (url: string) => {
setUrl(url);
};
return { ...state, executeFetch };
}
Usage would be useDataFetchFromAPI('url', []).
I figured you could do something like useDataFetchFromAPI < SomeType > () and pass it through but I'm unsure on the implementation.
Solution 1:[1]
useReducer accepts a type parameter of R extends Reducer<any, any> by which you can set a generic type to describe the state and actions.
IMO, closures are not meant to be used for typing purposes. Closures have effects in runtime while types are checked and removed from the code in compile time.
You could pass React.Reducer<State<T>, Action<T>> as the type parameter to useReducer in order to let TypeScript understand the proper types of the state and the actions.
E.g.
const [state, dispatch] = useReducer<React.Reducer<State<T>, Action<T>>>(reducer, initArgs)
Solution 2:[2]
Generic reducer can be written like that. An example from my app. Note reducer don't have to be inlined.
const reducer = <T>(state: T, action: T) =>
dequal(state, action) ? state : action;
const [data, dispatch] = useReducer<Reducer<T | null, T | null>>(
reducer,
null,
);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Hashem Qolami |
| Solution 2 | Daniel Steigerwald |
