Reputation:
With this code:
const Products = () => {
const classes = useStyles();
const { data } = useFetch('/categories');
return (
<div className={classes.root}>
<GridList cellHeight={180} className={classes.gridList}>
<GridListTile key="Subheader" cols={6} style={{ height: 'auto' }} />
{data &&
data.map((category: CategoryInterface) => {
return (
<GridListTile key={category.id}>
<GridListTileBar title={category.name} />
</GridListTile>
);
})}
</GridList>
</div>
);
};
I get 'Object is possibly null' on data before the map, I tried using the && operator to get rid of it and I also tried defining a variable like const categories = data as CategoryInterface[]
but that showed me I had to convert to unknown first, how should I do this instead?
Here is the useFetch hook
import { useEffect, useState } from 'react';
export const useFetch = (url: string) => {
const [state, setState] = useState({ data: null, loading: true });
useEffect(() => {
setState(state => ({ data: state.data, loading: true }));
fetch(url)
.then(res => res.json())
.then(json => setState({ data: json, loading: false }));
}, [url, setState]);
return state;
};
Upvotes: 5
Views: 8176
Reputation: 20755
Your useFetch
hook should be like this:
import { useEffect, useState } from 'react';
export const useFetch = (url: string) => {
const [state, setState] = useState({ data: {}, loading: true }); //Don't assign null to state variable
useEffect(() => {
setState(state => ({ data: state.data, loading: true })); //Remove this line, this is not needed
fetch(url)
.then(res => res.json())
.then(json => setState({ data: json, loading: false }));
}, [url, setState]); //Replace [url, setState] with [] as your second argument to make useEffect work for first time.
return state;
};
And your usage should be:
const { data } = useFetch('/categories');
{data.length>0 &&
data.map((category: CategoryInterface) => {
return (
<GridListTile key={category.id}>
<GridListTileBar title={category.name} />
</GridListTile>
);
})
}
Upvotes: 1
Reputation: 11848
Typescript assign types to JavaScript variables. Mostly when you use TS you should define variables types before usage. However TS sometimes can infer types of variables if possible.
It seems that you copy-paste JS code into TypeScript and trying to make it work. So first of all I suggest to define types for variables you're going to use, so TS will set correct types.
Initial call to useState
makes state.data
of type null
(that is the only TS knows about it type). And unlike JS, TS doesn't allow to change type during execution. So state.data
will have null
type during program execution.
const [state, setState] = useState({ data: null, loading: true });
// Type of state is
// { data: null, loading: boolean }
To correct this you should provide type of your state
variable ahead of time. One possible value of it is null
. Another possible value - is data received from fetch
. You probably should know what is type JSON structure of returned data from fetch
so type it accordinately.
From your code I can guess, that data type will probably look like this
type IData = CategoryInterface[];
And CategoryInterface
may look like
interface CategoryInterface {
id: string;
name: string;
}
So IData
will be second possible type of state.data
. So assign type of state
during useState
call
const [state, setState] = useState<{data: IData | null, loading: boolean}>({ data: null, loading: true });
But you should leave in place {data && data.map (/*...*/)}
as state.data
will be undefined until data fully loaded.
Full code
interface CategoryInterface {
id: string;
name: string;
}
type IData = CategoryInterface[];
export const useFetch = (url: string) => {
const [state, setState] = useState<{ data: IData | null, loading: boolean }>({ data: null, loading: true });
useEffect(() => {
setState(state => ({ data: state.data, loading: true }));
fetch(url)
.then(res => res.json())
.then(json => setState({ data: json, loading: false }));
}, [url, setState]);
return state;
};
Upvotes: 2