React generic useFetch state - reactjs

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

Related

How to modify data received from fetch Request in React

The question is how to modify data received from fetch Request in React?
1)I get the result of a fetch request and put in into state
2)To work with the data I need to modify it (add an ID and a boolean to each item of the array inside one of the objects of the coming array.
I tried the following way, but it doesn't work:
useEffect(() => {
fetch(
"https://opentdb.com/api.php?amount=5&category=10&difficulty=easy&type=multiple"
)
.then((res) => res.json())
.then((data) => data.map(question => ({
...question,
incorrect_answers: question.incorrect_answers.map(answer => {...answer, isSelected: false})
})));
}, []); ```
Your data is a object so you can't map over it. The data object has a property results which contains the array of questions.
data.results.map((question) => ({ ... })
Then the first time you correctly surround the object you return with () but when you map over the incorrect_answers you don't.
incorrect_answers: question.incorrect_answers.map((answer) => ({
...answer,
isSelected: false,
}));
Full code
useEffect(() => {
fetch(
"https://opentdb.com/api.php?amount=5&category=10&difficulty=easy&type=multiple"
)
.then((res) => res.json())
.then((data) =>
data.results.map((question) => ({
...question,
incorrect_answers: question.incorrect_answers.map((answer) => ({
answer,
isSelected: false,
})),
}));
);
}, []);

React: Fetched data, but no access to object properties

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.

Need a solution to fetch data from array and then push it to state using for each

Need a solution to fetch data from api and then push it to a state using forEach statement only
constructor() {
super()
this.state = {
char: [],
loading: false
}
}
componentDidMount() {
this.setState({loading: true})
fetch("/*someURL*/")
.then(response => response.json())
.then(data => {
data.forEach(item =>{
this.setState({
loading: false,
char: this.state.char.push(item)
})
})
})
}
render() {
const text = this.state.loading ? "loading..." : this.state.character.data
return (
<div>
<p>{text}</p>
</div>
)
}}
the data in my URL is:
{"page":1,"per_page":6,"total":12,"total_pages":2,"data":[{"id":1,"email":"#reqres.in","first_name":"abc","last_name":"cba"},{"id":2,"email":"#reqres.in","first_name":"def","last_name":"fed"},{"id":3,"email":"#reqres.in","first_name":"ghi","last_name":"ihg"},{"id":4,"email":"t#reqres.in","first_name":"jkl","last_name":"lkj""},{"id":5,"email":"#reqres.in","first_name":"mno","last_name":"onm"},{"id":6,"email":"#reqres.in","first_name":"pqr","last_name":"rqp"}]}
And can use forEach statement anywhere to get the data correctly
Try with this instead:
...
.then(data => {
this.setState({
loading: false,
char: [...this.state.char, ...data]
})
})
...
Updating answer to fit all your properties from your response. I assume this.state.char should only contain the data from result.data
//Notice that I changed the parameter name to result
.then(result => {
this.setState({
loading: false,
char: [...this.state.char, ...result.data],
page: result.page,
per_page: result.per_page,
total: result.total,
total_pages: result.total_pages
})
})
Alternatively you can just unfold the entire object:
.then(result => {
this.setState({
loading: false,
...result
})
})
But then your array will not be stored in this.state.char but rather this.state.data
For more information on the spread operator ... you can look here.
TLDR; Basically it takes any object or array {name: "Dennis", age: 29} unfolds it and merges it with whatever object you're spreading it "into":
const myObj = { ...{ name: "Dennis", age: 29"}, email: "myemail#example.com" }
=
const myObj = { name: "Dennis", age: 29, email: "myemail#example.com" }
You can use spread operator and simply do.
.then(data => {
const {char} = this.state;
this.setState({
loading: false,
char: [...char, ...data]
})
})
Try like this
fetch("/*someURL*/")
.then(response => response.json())
.then(result => {
this.setState({
loading: false,
char: [...this.state.char, ...result.data]
})
})
Below code used the spread operator to concatenate the existing state data with newly obtained data from API. It is called the spread(...) operator.
[...this.state.char, ...result.data]
For more details refer this link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Reducer not updating props in component correctly

My comments are dissappearing from my component after didMount() initializes them? It's really strange!
React component:
componentDidMount = (post) => {
const postId = this.props.post.id
console.log('postpreview', postId)
this.props.fetchComments(postId)
console.log('postpreview comments:', this.props.comments)
}
Redux Actions:
export const beginFetchComments = () => ({
type: C.BEGIN_FETCH_COMMENTS,
})
export const fetchCommentsFailed = (error) => ({
type: C.FETCH_COMMENTS_FAILED,
payload: { error },
})
export const fetchCommentsSuccess = (comments) => ({
type: C.FETCH_COMMENTS_SUCCESS,
payload: { comments }
})
export function fetchComments(postId) {
return dispatch => {
dispatch(beginFetchComments());
return fetch(`${api}/posts/${postId}/comments`, { headers })
.then(
res => res.json(),
error => console.log('An error occurred at fetchComments', error)
)
.then(json => {
dispatch(fetchCommentsSuccess(json));
return json;
});
};
}
Redux Reducer (switch case):
case C.BEGIN_FETCH_COMMENTS:
return {
...state,
loading: true,
error: null
};
case C.FETCH_COMMENTS_SUCCESS:
console.log(action.payload.comments);
const comments = _.mapKeys(action.payload.comments)
return {
...state,
loading: false,
comments,
};
The console displays this for the same console.log(), (I can't get my hands on my props!):
(2) [{…}, {…}]0: {id: "894tuq4ut84ut8v4t8wun89g", parentId: "8xf0y6ziyjabvozdd253nd", timestamp: 1468166872634, body: "Hi there! I am a COMMENT.", author: "thingtwo", …}1: {id: "8tu4bsun805n8un48ve89", parentId: "8xf0y6ziyjabvozdd253nd", timestamp: 1469479767190, body: "Comments. Are. Cool.", author: "thingone", …}length: 2__proto__: Array(0)
commentsReducer.js:22 []
I don't know what is the use of mapKeys here but what I would do is do a console.log to see if I'm getting an object and under what key there is a comments array:
case C.FETCH_COMMENTS_SUCCESS:
console.log(action.payload.comments); // is this logging an array?
return {
...state,
loading: false,
comments: action.payload.comments,
};
The bottom code I posted is the console.log - the object appears populated and then rerenders empty

React, redux, redux observable: Promise resolve type

I am trying to define the type of a a promise's resolve.
The following is the portion of the code or if you want to look it up on github: https://github.com/Electra-project/Electra-Desktop/blob/master/src/app/header/epics.ts
export function getStakingInfo(action$: ActionsObservable<HeaderActions>, store: any): any {
return action$.ofType(ActionNames.GET_STAKING_INFO)
.map(() => store.getState().electra.electraJs) // get electraJs object from the store
.filter((electraJs: any) => electraJs) // check if electraJs exists
.map(async (electraJs: any) => electraJs.wallet.getStakingInfo())
.switchMap(async (promise: Promise<WalletStakingInfo>) => new Promise((resolve) => {
promise
.then((data: WalletStakingInfo) => {
resolve({
payload: {
...data
},
type: ActionNames.GET_STAKING_INFO_SUCCESS
})
})
.catch((err: any) => {
resolve({
type: ActionNames.GET_STAKING_INFO_FAIL
})
})
}))
.catch((err: any) =>
Observable.of({
type: ActionNames.GET_STAKING_INFO_FAIL
}))
}
I am getting an error that says that resolve inside the portion of the above code is not type defined new Promise((resolve) => {. however I am unsure of the type of resolve.
Anybody can guide me as to what the type of resolve should be here?
You can just define your own type like this for example:
type Resolve = (action: { payload?: WalletStakingInfo; type: ActionNames; }) => void;
export function getStakingInfo(action$: ActionsObservable<HeaderActions>, store: any): any {
return action$.ofType(ActionNames.GET_STAKING_INFO)
.map(() => store.getState().electra.electraJs) // get electraJs object from the store
.filter((electraJs: any) => electraJs) // check if electraJs exists
.map(async (electraJs: any) => electraJs.wallet.getStakingInfo())
.switchMap(async (promise: Promise<WalletStakingInfo>) => new Promise((resolve: Resolve) => {
promise
.then((data: WalletStakingInfo) => {
resolve({
payload: {
...data
},
type: ActionNames.GET_STAKING_INFO_SUCCESS
})
})
.catch((err: any) => {
resolve({
type: ActionNames.GET_STAKING_INFO_FAIL
})
})
}))
.catch((err: any) =>
Observable.of({
type: ActionNames.GET_STAKING_INFO_FAIL
}))
}

Resources