I successfully fetched data from an API call, in console I see the result, but I can't access object properties or display them.
interface Data {
data: []
isLoading: boolean
}
function About() {
const [ dataUser, setDataUser ] = useState<Data>({data: [], isLoading: false})
useEffect(() => {
setDataUser({data: [], isLoading: true})
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => {
if (!response.ok) {
throw new Error("Failed with HTTP code " + response.status);
}
return response;
})
.then(response => response.json())
.then(data => setDataUser( {data: data, isLoading: false} ))
.catch((err) => {console.log(err)})
}, [])
const user = dataUser.data.map(item => {
console.log(item) //works
console.log(item.id) //doesn't work (error: "Property 'id' does not exist on type 'never'.")
return (<li>{item.name}</li>) //another error ("Objects are not valid as a React child...")
})
return (
<div>
{user}
</div>
)
}
export default About
EDIT
console.log(dataUser.data) ->
I added code for checking if response was ok and everything is fine with that.
console.log(item) ->
Your issue is your interface. I hadn't realized this was a compile-time error, not a run-time error.
interface Data {
data: any[],
isLoading: boolean,
}
The issue is that you're defining Data.data to be an empty array, and the elements of an empty array are of type never. That means that item in your map() callback is of type never, and never has no properties.
You probably want Data.data to be something other than any[] so that item has strongly-typed values, but this should unblock you.
Related
I'm trying to write a custom hook useFetch that makes many requests for different data.
The problem is that I can't typing my date correctly, I have state like this.
interface IState<T> {
data: T | T[]
loading: boolean,
error: boolean,
}
Initial state
const initialState = {
data: [],
loading: false,
error: false,
}
useEffect
useEffect(() => {
setData((prevState) => ({...prevState, loading: true}));
axios.get(url)
.then(response => response.data.results ? response.data.results as T[] : response.data as T)
.then((data) => (setData((prevState) => ({...prevState, loading: false, data}))))
.catch(e => {
setData((prevState) => ({...prevState, error: true}));
if (e instanceof Error) console.log(e.message);
})
}, []);
When i get response.data.results i have array of objects and when i have response.data i have only one object. I'm trying to return the right type.
I call my function like this useFetch
and I have result like this MovieType | MovieType[].
I don't need TypeGuard I need only one correct type.
If you see any problems please let me know.
Thanks a lot!
It's impossible for this example to have a singular type when unionized.
The generic function has no idea which one it will output as T[] or a T object because of how you defined IState.
You would just have to check if it's an array or not when you read .data from the hook.
The only way to have a singular output type would be to remove the union T[] and simply cast both like Sana Mumtaz said.
interface IState<T> {
data: T
loading: boolean,
error: boolean,
}
const useFetch = <T>(): IState<T> => {
// ...
response => (response.data.results ? response.data.results : response.data) as T
}
useFetch<MovieType[]>();
Axios.get<T> is a generic function, so you can take advantage of this.
Try
useEffect(() => {
setData((prevState) => ({ ...prevState, loading: true }));
axios.get<{ results: T[] } | T>(url)
.then(({ data }) => "results" in data ? data.results : data)
.then(data => {
setData(prevState => ({ ...prevState, loading: false, data }))
})
.catch(e => {
setData((prevState) => ({ ...prevState, error: true }));
if (e instanceof Error) console.log(e.message);
})
}, []);
See playground example here
I am trying to add 2 new properties to each array item within my object before saving it to the state. I need to add Value & Label properties to the Declarations array with the value of the Declarations.countryName. Tried the code below, but not to sure how to achieve this:
public getProfile() {
axios
.post('https://func-portal-dev.azurewebsites.net/api/GetUserProfile',
{
"EmailAddress": "benn.king#erfdfd.co.uk"
})
.then(res => {
console.log('Profile.data:', res.data);
this.setState({profile: res.data.map(p => {
return {
...p,
// New properties I am trying to create
p.Declarations.Value: p.Declarations.countryName,
p.Declarations.Label: p.Declarations.countryName,
}
})
})})
// Error catching
.catch(error => this.setState({ error, isLoading: false }));
}
try this
public getProfile() {
axios
.post('https://func-portal-dev.azurewebsites.net/api/GetUserProfile',
{
"EmailAddress": "benn.king#erfdfd.co.uk"
})
.then(res => {
console.log('Profile.data:', res.data);
let profileData = {...res.data};
profileData.Declarations = res.data.Declarations.map((item)=>{
const newDec = {...item}
newDec.Value = item.countryName;
newDec.Label = item.countryName;
return newDec
})
this.setState(profileData)
})})
// Error catching
.catch(error => this.setState({ error, isLoading: false }));
}
I'm using axios in order to fetch some objects, i.e:
zarusd:cur: Object
usdzar:cur: Object
zartry:cur: Object
tryzar:cur: Object
zarsek:cur: Object
sekzar:cur: Object
zarrub:cur: Object
I'm setting my state property to be an array like so:
this.state = {
currecnyData: [],
};
But when I console.log typeof after making the request I get an object. How can I make the state property currecnyData an array hosting each object as an array item so I can use a map method on it?
This is my axios call (without the 'options' parameters):
axios
.request(options)
.then((response) => {
this.setState(
{
currecnyData: [response.data.result],
},
() => {
console.log(typeof this.state.currecnyData);
}
);
})
.catch(function (error) {
console.error(error);
});
this.setState(
{
currecnyData: Object.entries(response.data.result),
}
Object.entries solved my issue! Now I get an array of all the objects.
Object.entries will convert object into an array as you wanted it to be
axios.request(options)
.then((response) => {
this.setState(
{
currecnyData: Object.entries(response.data.result); ,
},
() => {
console.log(typeof this.state.currecnyData);
}
);
})
.catch(function (error) {
console.error(error);
});
I have a component <ItemPage /> rendering a list of items and a form to add an item.
render() {
return (
<div>
<ItemsList />
<AddItemForm />
</div>
)
}
My <ItemsList /> has got a componentDidMount() method that calls the redux props function this.props.items() to list the items when the component is mounted.
When an item is added using <AddItemForm /> component, the item is added to the database with an API POST request.
return dispatch => {
axios(options)
.then(response => {
dispatch({
type: ITEM_ADDED,
payload: response.data
});
})
.catch(error => {
return dispatch(handleError(error))
});
}
How am I supposed to refresh the list :
should I make an API GET request to get the updated item list ?
should I use the response.data which is supposed to send the item object that has just been created ?
Thank you for your help.
// ==================== EDITS ====================
If I choose the first option, the following code works:
return dispatch => {
axios(postOptions)
.then(response => {
dispatch({
type: ITEM_ADDED,
payload: response.data
});
// getItems(); // <= unfortunately, this doesn't work.
axios(getOptions)
.then(response => {
dispatch({
type: ITEMS_FETCHED,
payload: response.data
});
})
.catch(error => {
return dispatch(handleError(error))
});
})
.catch(error => {
return dispatch(handleError(error))
});
}
There is ugly code repetitions, knowing I already have a fetch items function:
export function getItems() {
...
return dispatch => {
console.log('I am here') // <= when getItems() is inside addItem() function, 'I am here' is never displayed.
axios(options)
.then(response => {
dispatch({
type: ITEMS_FETCHED,
payload: response.data
});
})
.catch(error => {
return dispatch(handleError(error))
});
}
}
How can I fix this please ?
And how to follow the "a function should do only one thing" principle ?
I’d recommend making a GET request after a successful POST request. This ensures that you’re getting the most up to date items objects from your API.
If you want to save yourself a GET you can take the response from your POST and append it to your items reducer state value. There isn’t really a downside to this I just always prefer to use the GET since you’ve already declared an action for it and unless your API is massive it shouldn’t lead to any extended or unnecessary loading times.
EDIT
The way you were currently using it, you won't hit getItems because it's async. Try putting the getItems in another then. See below.
return dispatch => {
axios(postOptions)
.then(response => {
dispatch({
type: ITEM_ADDED,
payload: response.data
});
})
.then(()=>getItems())
.catch(error => {
return dispatch(handleError(error))
});
}
And in getItems replace options with getOptions.
I bulid an api using laravel which can run in postman (http://lkcfesnotification.000webhostapp.com/api/notifications). The problem is when i fetch using an example from this (https://www.youtube.com/watch?v=IuYo009yc8w&t=430s) where there is a array in the api then i have to setstate the array which is working well but when i try using the below code it does not render due to it is not using array in the api for example the random user api have "results" :[item], and mine one is "data":[my item]
fetchData = async () => {
const response = await fetch("https://randomuser.me/api?results=500");
const json = await response.json();
this.setState({ data: json.results });
};
if i use this will work but i want to use below code due to some homework i am doing
type Props = {};
export default class IndexScreen extends Component<Props> {
...
this.state = {
data: [],
isFetching: false,
};
_load() {
let url = "http://lkcfesnotification.000webhostapp.com/api/notifications";
this.setState({isFetching: true});
fetch(url)
.then((response) => {
if(!response.ok) {
Alert.alert('Error', response.status.toString());
throw Error('Error ' + response.status);
}
return response.json()
})
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
.catch((error) => {
console.log(error)
});
}
https://imgur.com/a/he5mNXv this is my render
the result i get the code i run is blank is loading
The fetch request is working but you are not saving the right data in the right state property.
The issues is located in the following part:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
You are assigning the response to a variable members but saving another variable data, which does not exist.
In addition, the response is an object with more information than just the data, so what you are looking for is just the data property of the response.
This should work:
.then(({ data }) => {
this.setState({data});
this.setState({isFetching: false});
})
Here we destructure the response into the variable { data }, solving your issue.
Based on the snippets you don't use the fetched data to set it to your state:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
membersis the result of your fetched json. So either rename members to data or use data: members. If the code should work like your first function it's probably data: members.result. You can also combine the two setState calls to one single call:
this.setState({
data: members.result,
isFetching: false,
});