How to add fresh data to an existing data in React-query? - reactjs

I have a code which calls an API with a certain number of data and receives them. I wish for the 2nd call to add the newly retrieved data to be added to the already existing data using React-query library.
My code so far is :
const fetchData = async ({queryKey : [key , pages]}) => {
return (await fetch(`http://jservice.io/api/random?count=${pages}`)).json()
}
function App() {
const [state, setState] = React.useState(10);
const {isLoading, error, data , } = useInfiniteQuery(['general' , state], fetchData , {keepPreviousData : true} )
setTimeout(() => {
setState(20)
}, 4444);
console.log(data)
return (
<div className="App">
h
</div>
);
}
I wish to hit the 4444 time line marker by adding the newly fetched data to the existing data solely by using React-query hooks.
How can I achieve this?

You need to use the fetchNextPage from the query. Call this to load the next page. Either onClick or after a timeout.
<button onClick={fetchNextPage}>Load more</button>
Also do not set setTimeout in the render function, since it will be called and executed on every render, which will remove your progress.
Dont track the current page yourself but let react-query handle it.
Just set the getNextPageParam function to return how you need to get the next page and extract that as pageParam from the query context in fetchData
To render the data, you need to map over the pages returned from the query inside data and for each page, query over the items returned:
{isSuccess &&
data.pages.map((page, index) => (
<div key={index}>
{page.results.map((poke) => (
<div key={poke.name}>{poke.name}</div>
))}
</div>
))}
Here is a sandbox with pokemon.

Related

Can anyone explain.Why the 'res" is getting logged two times?

I am trying to get the images data from flickr. I am getting the response but not able to display the image.I have tried using useEffect to fetch the api and useState variable to store images array. Still it was getting called two times.
const DisplayImages = () => {
let photos = [];
fetch(
` https://www.flickr.com/services/rest/?method=flickr.galleries.getPhotos&api_key=***********************&gallery_id=************&format=json&nojsoncallback=1`
)
.then((response) => response.json())
.then((res) => {
const images = res;
console.log(res);
photos = images.photos.photo;
});
return (
<>
<div className="imagescontainer">
{photos.map((photo) => {
console.log(photo);
let url = `https://farm${photo.farm}.staticflickr.com/${photo.server}/${photo.id}_${photo.secret}`;
<>
{/* <img src={require(url)} alt="img" /> */}
</>
})}
</div>
</>
);
};
I expected the images to be rendered.
It's getting called twice because React is probably configured to be in strict mode in development, which causes hooks to be called twice.
Second, you're using a locally-scoped variable photos to hold state, when you need to use useState instance, otherwise React won't know when the state changes and re-render the component.
Third, it looks like you're setting photos, which is initialized with an empty array, to a single instance of a photo.

React hooks not showing correct data when component is rendered

I have created a hook in a component as below.
useEffect(() => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
getStatusCode(response.data.code);
console.log("responseCode",responseCode);
getMatchdata(response.data.result);
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
})
.catch((error) => console.log(error));
},[]);
This is a state function used in the above effect
const [info, setInfo] = useState();
I expected the above useEffect should return me some data in the below block
<div> {info} </div>
but it is showing wrong data, whereas I have created another function to trigger on Refresh button as
function refresh() {
setInfo(<MatchData responseCode={responseCode} matchdata={matchdata} />);
}
this function is returning me correct data. I want to create a functionality that will dynamically update the div element with change in state of {info}, by default when the page is loaded first, it should fetch data from the endpoint used here only. I'm new to React. Where I'm going wrong and how do I achieve it?
I don't want to say this is wrong, but this seems like an atypical approach from what I've seen in the wild. Specifically I am talking about storing a JS/JSX or TS/TSX element in a state object. I have more commonly seen a value stored in that type of variable and that value changing when necessary via the set dispatch function. Then the state object is passed to the component who needs it to do something. In react, when the value of that state object changes, it will cause the component who uses it to re-render. If I were coding this, this is what my code would look like.
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
Unless you only specifically want something to happen once at component mount, you would not use useEffect() like you did in your code. If the decision to refresh were coming from an external object with state instead of the refresh button, you could add that object whose state changes to the dependency array of the useEffect function. This would cause the refresh to run any time that object's state value changes. In the code above, getData() (which might need to be async) will only run when called. Then you have a component called divComponent which is expecting info to have value. When rendering this component you would want a null check like I coded below. Finally the refreshButton component will call getData() when it is clicked.
Then in your code that renders this, I would have something like this:
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
The code above will check if the state object info has value, and if it does it will render the divComponent with your data values. If it does not, instead it will show the p tag explaining that there is no data. Either way it will render the refreshButton, which would run the getData() function again when clicked.
** EDIT **
Based on your comment, here is another approach so you can have a value on page load and update when necessary:
import {useState, useEffect} from "react";
const [info, setInfo] = useState();
const getData = () => {
axios
.get("http://127.0.0.1:5000/v1/matches")
.then((response) => {
setInfo(response.json())
})
.catch((error) => console.log(error));
}
useEffect(()=> {
getData();
}, [])
const divComponent = ({info}) => (
<div>
<p>{info.data.code}</p>
<p>{info.data.result}</p>
</div>
)
const refreshButton = () => (
<button onClick(()=>getData())>Refresh</button>
)
export const Page = () => (
<>
{info ? <divComponent info={info} /> : <p>There is no info</p>}
<refreshButton />
</>
);
your method is quite complex. I believe you need to add your MatchData Component Inside the div in this way.Also Don't Need To Call State Method setInfo() in useEffect hook.Only responseCode and matchdata Needed that is already adjusted by you in useEffect Hook.

Can't Update state after fetch request

I'm trying to make an app where I fetch data from a Graphql API, and update state after fetching, but after the successful request I'm getting the data but I can't update state with that data. when I console log the response data it's showing the array with required data but when I update state and console log the state it's showing empty array.
I need to update the state to use it in the component, when i'm doing this it's throwing error that currList is undefined.
here are pictures of code and console.
export default function App() {
const [search, setSeach] = useState("");
const [currList, setCurrList] = useState([]);
const fetchShips = async () => {
const response = await request(gql`
{
ships {
name
home_port
image
}
}
`);
console.log("request response", response.data);
setCurrList(response.data);
console.log("currlist:", currList);
};
useEffect(() => {
fetchShips();
}, [currList]);
const Searchitems = (event) => {
setSeach(event.target.value);
setCurrList(
currList.filter((item) => {
return item.name.includes(search) || item.home_port.includes(search);
})
);
};
return (
<div className="App">
<div className="container">
<header></header>
<div className="body">
<input
type="text"
id="search"
value={search}
onChange={Searchitems}
className="input"
placeholder="Search Ships"
/>
<p>Total Count: {currList.length}</p>
<div className="ships">
{currList.map((item, index) => {
return (
<Ship
key={index}
name={item.name}
port={item.port}
img={item.image}
/>
);
})}
</div>
</div>
</div>
</div>
);
}
the State is Updated just not when the code is running that's why it logs that the state is an empty array, try to console.log it once again and you will see that there is something in the List.
That's normal, everything is happening asynchronously. React will trigger a re-render once your currList is updated and you will get its new value. If you want to listen to this change, you have to use useEffect with currList as a dependency.
useEffect(() => {
console.log("List is updated", currList);
}, [currList]);
The function fetchShips has closure over the currList state variable that blongs to specific render call, so when you log it after calling setCurrList it shows you the previous state not the updated state, you can log it in the useEffect to see the changes.
I had the same problem once. I couldn't find efficient solution but my solution saved me anyway.
I used useRef instead of useState.
Try this:
const currList = useRef([]);
useEffect=(()=>{
const fetchShips = async () => {
const response = await request(gql`
{
ships {
name
home_port
image
}
}
`);
console.log("request response", response.data);
// setCurrList(response.data);
if(response)
currlist.current = response.data
console.log("currlist:", currList);
};
fetchShips()
// Also as far as I know ,you should use useEffect like this
},[])
//... codes
return(
//... codes
currList.current.map(...
)
//... codes
Before using useRef, try to define your fetchShips function inside useEffect so maybe you don't need my solution.
Why is not efficient my solution for your case?
When you want to update your currList data, useRef does not trigger re-render. Even if your data updated, you cannot see it on your screen.
So setCurrList(currList.current) can save you but as I said earlier it may not efficient way.

How to update React state once useMutation is done?

I add an user to an api with react-query's useMutation hook. It works. Now, I need to add the new user to my array of users, which is in my state.
I know I'm supposed to query all the users with useQuery and then use onSuccess inside useMutation to modify the cache. But in certain cases, I don't fetch the users with useQuery, so I need to update a local state as I would do with a normal promise.
For the moment, I simply check if the prop "success" is true and if so, I update the array. But it only works on the second click. Why and how to fix this?
It seems the success condition inside onAddUser() is only reached on a second click.
export default function App() {
const { updateUser } = userService();
const { update, loading, error, data, success } = updateUser();
const [users, setUsers] = useState(allUsers);
const onAddUser = async () => {
await update(newUser);
if (success) {
return setUsers((users) => [...users, data]);
}
};
return (
<>
<div>
{users.map((user) => (
<div key={user.id}>
{user.name} - {user.job}
</div>
))}
</div>
{loading && <div>sending...</div>}
{error && <div>error</div>}
<button onClick={() => onAddUser()}>add user</button>
</>
);
}
Here is also a sandbox: https://codesandbox.io/s/usemutation-test-co7e9?file=/src/App.tsx:283-995
The success prop returned from updateUser (I assume this is somehow the result returned by useMutation) will only update on the next render cycle. Functions will still keep the reference to whatever they closured over, even if you have an async await in there. This is nothing react-query specific, this is just how react works.
I would suggest to use the onSuccess callback of the mutation function, or use mutateAsync and check the result, though you have to keep in mind to catch errors manually if you use mutateAsync. You can read about mutateAsync here, I'm gonna show you a mutate example:
const { mutate, loading } = useMutation(() => ...);
const onAddUser = () =>
mutate(newUser, {
onSuccess: (newData) => setUsers((users) => [...users, data]);
});
};
also, please don't violate the rules of hooks. You can only call hooks from functional components or other hooks (=functions that start with use), but in your codesandbox, you call useMutation from the updateUser function ...

Fetching w/ custom React hook based on router parameters

I'm trying to fetch data with a custom React hook, based on the current router parameters.
https://stackblitz.com/edit/react-fetch-router
What it should do:
On first load, check if URL contains an ID...
If it does, fetch a todo with that ID
If it does not, fetch a todo with a random ID & add ID to url
On fetch button clicks...
Fetch a todo with a random ID & add ID to url
What is wrong:
Watching the console or inspector network tab, you can see that it's firing several fetch requests on each click - why is this and how should this be done correctly?
Since you used history.push on handleClick, you will see multiple requests being sent as you are using history.push on click handler which will cause a re-render and make use use random generated url as well as also trigger the fetchTodo function.
Now a re-render will occur which is cause a randomised id to be generated. and passed onto useTodo hook which will lead to the fetchTodo function being called again.
The correct solution for you is to set the random todo id param on handleClick and also avoid unnecessary url updates
const Todos = () => {
const history = useHistory();
const { id: todoId } = useParams();
const { fetchTodo, todo, isFetching, error } = useTodo(todoId);
const isInitialRender = useRef(true);
const handleClick = () => {
const todoId = randomMax(100);
history.push(`/${todoId}`);
};
useEffect(() => {
history.push(`/${todoId}`);
}, []);
useEffect(() => {
if(!isInitialRender.current) {
fetchTodo();
} else {
isInitialRender.current = false
}
}, [todoId])
return (
<>
<button onClick={handleClick} style={{marginBottom: "10px"}}>Fetch a todo</button>
{isFetching ? (
<p>Fetching...</p>
) : (
<Todo todo={todo} color={todo.color} isFetching={isFetching} />
)
}
</>
);
};
export default Todos;
Working demo

Resources