i made a custom hook for fetching data the problem is when i use <React.StrictMode> the fetch singal for aborting gets fire but some how it works if i remove strict mode
this is the fetch hook
import { useEffect, useReducer } from 'react';
import { ApiResponse } from '../interfaces/ApiResponse';
const initialState: ApiResponse = {
loading: false,
data: null,
error: null,
};
type Action =
| { type: 'start' }
| { type: 'error'; payload: Error }
| { type: 'success'; payload: JSON };
const reducer = (state: ApiResponse, action: Action) => {
switch (action.type) {
case 'start':
return {
loading: true,
data: null,
error: null,
};
case 'success':
return {
loading: false,
data: action.payload,
error: null,
};
case 'error':
return {
loading: false,
data: null,
error: action.payload,
};
default:
return state;
}
};
export const useFetch = (url: string): ApiResponse => {
const [response, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const controller: AbortController = new AbortController();
const signal: AbortSignal = controller.signal;
const fetchData = async () => {
dispatch({ type: 'start' });
try {
const response: Response = await fetch(url, { signal: signal });
if (response.ok) {
const json = await response.json();
dispatch({
type: 'success',
payload: json,
});
} else {
dispatch({
type: 'error',
payload: new Error(response.statusText),
});
}
} catch (error: any) {
dispatch({
type: 'error',
payload: new Error(error),
});
}
};
fetchData();
return () => {
controller.abort();
};
}, [url]);
return response;
};
when i call this hook in one of my components like this:
const Grid = () => {
const response = useFetch(`${BASE_API_URL}/games`);
useEffect(() => {
console.log(response);
}, [response]);
return (
<div className='grid__wrapper'>
<div className='grid__content'>
{response.loading && <h4>Loading...</h4>}
<h4>helo</h4>
</div>
</div>
);
};
export default Grid;
the response.loading is never set to true and i can see an abort error in the logs but if i remove strict mode it works fine
Related
I'm using Apollo client GraphQL with React Native,
I have a query that launches but data stays undefined, I get data only when I change fetch policy from network-only to cache-only, but then if I logout and login the problem persists and I get nothing.
This is how the component works
// in MyComponent.tsx
export default function MyComponent(): JSX.Element {
const [flashCards, setFlashCards] = useState<any>([]);
const { loading, error, data, refetch } = useQuery<
{
getAllFlashcards: {
id: string;
flashcard: Array<{ id: string; title: string }>;
};
},
{ classroomId: string }
>(GET_ALL_FLASH_CARDS, {
fetchPolicy : "network-only", //<--------- 1. if i comment this
fetchPolicy: "cache-only", //<------- 2. then uncomment this then i get data
variables: {
classroomId: classroomId,
},
});
if (loading && data) {
console.log("loading and adata");
}
if (loading && !data) {
console.log("loading and no data");
}
if (error) {
console.log(error);
}
useEffect(() => {
if (data) {
setFlashCards(data.getAllFlashcards);
}
}, [data]);
return(<>...<>)
}
I followed Apollo client docs when implementing the authentication by clearing the store when I signin and signout, but still... the problem persist
// in App.tsx
export default function App() {
const [classroomId, setClassroomId] = useState<any>("");
const [state, dispatch] = React.useReducer(
(prevState: any, action: any) => {
switch (action.type) {
case "SIGN_IN":
return {
...prevState,
isSignout: false,
userToken: action.token,
};
case "SIGN_OUT":
return {
...prevState,
isSignout: true,
userToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
}
);
//passed to the application using a context provider.
const auth = React.useMemo(
() => ({
signIn: async (data: any) => {
await client.resetStore();
await SecureStore.setItemAsync("userToken", data.token);
dispatch({ type: "SIGN_IN", data });
},
signOut: async() => {
await client.resetStore();
await SecureStore.deleteItemAsync("userToken")
dispatch({ type: "SIGN_OUT" })
}
}),
[]
);
Why fetched data is undefined but visible only when I change fetch policy even though I am using fetchPolicy : "network-only" ?, your help is appreciated, thank you.
I everyone. I try to post simple login form with a custom fetch hook. The login works fine if I authenticate well at the first time. But in error case if I try to revalidate the form the component seems to be unmounted (hooks clean up function is called) and i can't refetch my api. Why this component is unmounted i don't understand ? Thanks for your help
signin.ts (the code has been simplified)
import React, { useState } from 'react'
import useFetch from '../shared/hooks/useFetch'
const Signin: React.FunctionComponent = () => {
const [postData, setPostData] = useState({
url: "",
options: {}
})
type ResponseT = {
id: string,
firstname: string,
lastname: string,
roles: string[],
accessToken: string
} & string
const { data, error } = useFetch<ResponseT>(postData.url, postData.options)
const handleSubmit = (e: any) => {
if (e) e.preventDefault()
setPostData({
url: `${process.env.REACT_APP_API_HOST}/signin`,
options: {
method: "POST",
body: JSON.stringify({
email: "testt#test.fr",
password: "bla"
})
}
})
}
return (
<form>
<div>
<div className="form-group">
<button onClick={handleSubmit} className="btn btn-primary btn-block">Sign In</button>
{error && <p>{error}</p>}
{data === '' && <p style={{ height: '100px', backgroundColor: 'red' }}>Loading...</p>}
</div>
</div>
</form>
)
}
export default Signin
useFetch.ts
import { useEffect, useReducer, useRef } from 'react'
interface State<T> {
data?: T
error?: Error
}
type Cache<T> = { [url: string]: T }
type Action<T> =
| { type: 'loading' }
| { type: 'fetched'; payload: T }
| { type: 'error'; payload: Error }
function useNiceFetch<T = unknown>(url?: string, options?: RequestInit): State<T> {
const cache = useRef<Cache<T>>({})
const cancelRequest = useRef<boolean>(false)
const initialState: State<T> = {
error: undefined,
data: undefined,
}
const fetchReducer = (state: State<T>, action: Action<T>): State<T | any> => {
switch (action.type) {
case 'loading':
return { ...initialState, data: '' }
case 'fetched':
return { ...initialState, data: action.payload }
case 'error':
return { ...initialState, error: action.payload }
default:
return state
}
}
const [state, dispatch] = useReducer(fetchReducer, initialState)
useEffect(() => {
if (!url) return
const fetchData = async () => {
if (cancelRequest.current) return
dispatch({ type: 'loading' })
if (cache.current[url]) {
dispatch({ type: 'fetched', payload: cache.current[url] })
return
}
try {
const response = await fetch(url, options) as any
const responseJson = await response.json()
if (!response.ok) {
throw responseJson
}
cache.current[url] = responseJson as T
dispatch({ type: 'fetched', payload: responseJson })
} catch (error) {
if (cancelRequest.current) return
dispatch({ type: 'error', payload: error as Error })
}
}
fetchData()
return () => {
cancelRequest.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url, options])
return state
}
export default useFetch
Here's the error that the response looks like an empty array not a number "the length"
I want to display collection length from mongodb in react with redux.
Here's my code:
Action:
carActions.js
export const howMany = () => async (dispatch) => {
try {
dispatch({ type: CAR_NUMBER_REQUEST })
const { data } = await axios.get('/api/cars')
dispatch({ type: CAR_NUMBER_SUCCESS, data })
} catch (error) {
dispatch({
type: CAR_NUMBER_FAIL, payload: error.message})
}
}
Reducer:
carReducer.js
export const carNumberReducer = (state = { cars: [] }, action) => {
switch (action.type) {
case CAR_NUMBER_REQUEST:
return { loading: true, cars: [] }
case CAR_NUMBER_SUCCESS:
return { loading: false, success: true, cars: action.payload }
case CAR_NUMBER_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
Here where it would be displayed:
carScreen.js
const dispatch = useDispatch()
const carNumber = useSelector(state => state.carNumber)
const { cars } = carNumber
useEffect(() => {
dispatch(howMany());
}, [dispatch])
return (
<div>
{cars.map((cars) => (
<p key={cars._id}>{cars}</p>
))}
</div>)}
I am trying to convert a useReducer hooks to TypeScript,
It's working fine as useReducer hooks, however not in TypeScript.
Here is my code,
import * as React from "react";
const JOKE_URL = "https://icanhazdadjoke.com/";
const initialState = { data: null, error: null, loading: true };
type ACTIONTYPE =
| { type: "fetch" }
| { type: "data"; data: object }
| {type: "error"};
function fetchReducer(state: typeof initialState, action: ACTIONTYPE) {
switch (action.type) {
case 'fetch':
return {
...state,
loading: true,
}
case 'data':
return {
...state,
data: action.data,
error: null,
loading: false,
}
case 'error':
return {
...state,
error: 'Error fetching data. Try again',
loading: false,
}
default:
return state;
}
}
function useFetch (url: string) {
const [state, dispatch] = React.useReducer(
fetchReducer,
{ data: null, error: null, loading: true }
)
React.useEffect(() => {
dispatch({ type: 'fetch' })
fetch(url, {
headers: {
accept: "application/json"
}
})
.then((res) => res.json())
.then((data) => dispatch({ type: 'data', data }))
.catch((e) => {
console.warn(e.message)
dispatch({ type: 'error' })
})
}, [url])
return {
loading: state.loading,
data: state.data,
error: state.error
}
}
export default function App() {
const { loading, data, error } = useFetch(JOKE_URL);
console.log(data);
if (loading === true) {
return <p>Loading</p>
}
if (error) {
return (
<React.Fragment>
<p>{error}</p>
</React.Fragment>
)
}
return (
<div>
<h1>{data.joke}</h1>
</div>
);
}
I am getting some errors like:
-> Argument of type '(state: { data: null; error: null; loading: boolean; }, action: ACTIONTYPE) => { data: null; error: null; loading: boolean; } | { data: object; error: null; loading: boolean; } | { error: string; loading: boolean; data: null; }' is not assignable to parameter of type 'ReducerWithoutAction'. TS2769
-> Expected 0 arguments, but got 1. TS2554
You should set return type for your reducer and all working fine , also i did change some of your state and types for cleaning your code :
import * as React from "react";
const JOKE_URL = "https://icanhazdadjoke.com/";
const initialState = { loading: true };
type initState ={
data?: any,
error?: string,
loading: boolean
}
type ACTIONTYPE =
| { type: "fetch" }
| { type: "data"; data: object }
| { type: "error"};
function fetchReducer(state: initState, action: ACTIONTYPE):initState {
switch (action.type) {
case 'fetch':
return {
...state,
loading: true,
}
case 'data':
return {
...state,
data: action.data,
loading: false,
}
case 'error':
return {
...state,
error: 'Error fetching data. Try again',
loading: false,
}
default:
return state;
}
}
function useFetch (url: string) {
const [state, dispatch] = React.useReducer(
fetchReducer,
initialState
)
React.useEffect(() => {
dispatch({ type: 'fetch' })
fetch(url, {
headers: {
accept: "application/json"
}
})
.then((res) => res.json())
.then((data) => dispatch({ type: 'data', data }))
.catch((e) => {
console.warn(e.message)
dispatch({ type: 'error' })
})
}, [url])
return {
loading: state.loading,
data: state.data,
error: state.error
}
}
export default function App() {
const { loading, data, error } = useFetch(JOKE_URL);
console.log(data);
if (loading) {
return <p>Loading</p>
}
if (error) {
return (
<React.Fragment>
<p>{error}</p>
</React.Fragment>
)
}
return (
<div>
<h1>{data.joke}</h1>
</div>
);
}
I have problem with destructuring Typescript data object from my own Hook created in React.
export interface InitialState {
pokemonListLoading: false;
pokemonListLoadingFailed: false;
data: [];
}
interface FetchPokemonList {
type: typeof FETCH_POKEMON_LIST;
}
interface FetchPokemonListSuccess {
type: typeof FETCH_POKEMON_LIST_SUCCESS;
payload: PokemonList;
}
...
export type PokemonListActionTypes = FetchPokemonList | FetchPokemonListSuccess | FetchPokemonListError;
const dataFetchReducer = (state: InitialState, action: PokemonListActionTypes) => {
switch (action.type) {
case FETCH_POKEMON_LIST:
return {
...state,
pokemonListLoading: true,
pokemonListLoadingFailed: false,
};
case FETCH_POKEMON_LIST_SUCCESS:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: action.payload,
};
case FETCH_POKEMON_LIST_ERROR:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: true,
};
default:
throw new Error();
}
};
export const fetchPokemonList = (initialUrl: string, initialData: []) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: initialData,
});
useEffect(() => {
const fetchData = async () => {
dispatch({ type: FETCH_POKEMON_LIST });
try {
const result = await axios(url);
dispatch({ type: FETCH_POKEMON_LIST_SUCCESS, payload: result.data });
} catch (error) {
dispatch({ type: FETCH_POKEMON_LIST_ERROR });
}
};
fetchData();
}, [url]);
return [state, setUrl];
};
and whole component
import React, { FunctionComponent } from 'react';
import { fetchPokemonList, InitialState } from '../../hooks/fetchPokemonList';
const PokemonList: FunctionComponent = () => {
const [{
data: { results: pokemonList },
pokemonListLoading,
pokemonListLoadingFailed,
},
] = fetchPokemonList('https://pokeapi.co/api/v2/pokemon',[]);
return (
<div>
PokemonList
{pokemonListLoading ? (
<div>Laoding...</div>
) : (
pokemonList && pokemonList.map((pokemon: { name: string}) => (
<div key={pokemon.name}>{pokemon.name}</div>
))
)}
{pokemonListLoadingFailed && <div>Error</div>}
</div>
)
}
export { PokemonList }
error code displayed by Webstorm
TS2339: Property 'data' does not exist on type '{ pokemonListLoading:
boolean; pokemonListLoadingFailed: boolean; data: []; } | {
pokemonListLoading: boolean; pokemonListLoadingFailed: boolean; data:
PokemonList; } | Dispatch ...>>'.
The issue is within this line:
dispatch({ type: FETCH_POKEMON_LIST_SUCCESS, payload: result.data });
Where you send as payload without using a key for your new data value.
Then in the code section you're setting data with the payload object, which results in the error you're experiencing:
case FETCH_POKEMON_LIST_SUCCESS:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: action.payload,
};
Try passing your payload like this: payload: { data: result.data }.
Then set your data respectively: data: action.payload.data