i am new to react query
i did not get any data return by useQuery hook but api request is working fine
const listofgroupsapi = async (lastId, limit, id) => {
const res = await axios.get(
`${apiURL}/groups/getAllGroups?lastId=-1&limit=10&id=${id}`
);
console.log(res.data);
return res.data;
};
const Superadminpanel = () => {
const [lastId, setLastId] = useState(0);
const [limit, setLimit] = useState(10);
const [id, setId] = useState(cookies.id);
const { isLoading, data } = useQuery("groups", () => {
listofgroupsapi(lastId, limit, id);
});
return (
<div style={{ minHeight: "90vh" }}>
<div>
<h1>here we are</h1>
{isLoading ? <h1>loading</h1> : <h1>not loading</h1>}
{data ? <h1>data</h1> : <h1>no data:{data}</h1>}
</div>
</div>
);
};
export default Superadminpanel;
console.log(res.data) gives me correct result from my api
response of my api
I don't know why useQuery doesn't give me any data
React query dev tool image
Your main issue is that you aren't returning the promise result from listofgroupsapi() but there are also other improvements you can make.
As per the react-query documentation...
If your query function depends on a variable, include it in your query key
Since query keys uniquely describe the data they are fetching, they should include any variables you use in your query function that change
With that in mind, you should use the following
const listofgroupsapi = async (lastId, limit, id) =>
(
await axios.get(`/groups/getAllGroups`, { // no need for template literals
baseURL: apiURL,
params: { // query params are easier and safer this way
lastId: -1, // not lastId?
limit: 10, // not limit?
id,
},
})
).data;
and in your component
const { isLoading, data } = useQuery(["groups", lastId, limit, id], () =>
listofgroupsapi(lastId, limit, id) // no {...} means implicit return
);
Related
const fetcher = (url: string) => fetch(url).then((r) => r.json());
const { data, error } = useSWR(
"https://some.com/api",
fetcher,
);
is there any way to add data in a useState hook like this
const fetcher = (url: string) => fetch(url).then((r) => r.json());
const { data, error } = useSWR(
"https://meme-api.herokuapp.com/gimme/5",
fetcher,
);
const [memes,setMemes]=useState(data);
cause I want to concat the data at some point for inifnite scrolling
Since https://meme-api.herokuapp.com/gimme/5 always returns new data for each call, useSWR isn't a good fit for this and, moreover, the fact it retrieves from cache and gives that to your code and then revalidates and (possibly) calls your code to update, without telling you whether it's the first result or an update, makes it very hard to do what you're describing.
Instead, I'd just use fetch directly and not try to do the SWR thing; see comments:
// Start with no memes
const [memes,setMemes] = useState([]);
// Use a ref to track an `AbortController` so we can:
// A) avoid overlapping fetches, and
// B) abort the current `fetch` operation (if any) on unmount
const fetchControllerRef = useRef(null);
// A function to fetch memes
const fetchMoreMemes = () => {
if (!fetchControllerRef.current) {
fetchControllerRef.current = new AbortController();
fetch("https://meme-api.herokuapp.com/gimme/5", {signal: fetchControllerRef.current.signal})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
})
.then(newMemes => {
setMemes(memes => memes.concat(newMemes.memes));
})
.catch(error => {
// ...handle/report error...
})
.finally(() => {
fetchControllerRef.current = null;
});
}
};
// Fetch the first batch of memes
useEffect(() => {
fetchMoreMemes();
return () => {
// Cancel the current `fetch` (if any) when the component is unmounted
fetchControllerRef.current?.abort();
};
}, []);
When you want to fetch more memes, call fetchMoreMemes.
Live Example:
const {useState, useEffect, useRef} = React;
const Example = () => {
// Start with no memes
const [memes,setMemes] = useState([]);
// Use a ref to track an `AbortController` so we can:
// A) avoid overlapping fetches, and
// B) abort the current `fetch` operation (if any) on unmount
const fetchControllerRef = useRef(null);
// A function to fetch memes
const fetchMoreMemes = () => {
if (!fetchControllerRef.current) {
fetchControllerRef.current = new AbortController();
fetch("https://meme-api.herokuapp.com/gimme/5", {signal: fetchControllerRef.current.signal})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
})
.then(newMemes => {
// I'm filtering out NSFW ones here on SO
setMemes(memes => memes.concat(newMemes.memes.filter(({nsfw}) => !nsfw)));
})
.catch(error => {
// ...handle/report error...
})
.finally(() => {
fetchControllerRef.current = null;
});
}
};
// Fetch the first batch of memes
useEffect(() => {
fetchMoreMemes();
return () => {
// Cancel the current `fetch` (if any) when the component is unmounted
fetchControllerRef.current && fetchControllerRef.current.abort();
};
}, []);
const message = memes.length === 1 ? "1 meme:" : `${memes.length} memes:`;
return <div>
<div>{message} <input type="button" value="More" onClick={fetchMoreMemes}/></div>
<ul>
{/* `index` as key is ONLY valid because our array only grows */}
{memes.map(({postLink}, index) => <li key={index}>{postLink}</li>)}
</ul>
</div>
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
The fastest solution to transfer data from one variable to another is to use an useEffect hook. When data changes, update memes.
useEffect(() => { setMemes(data); }, [data])
Infinite scroling
A better solution would be to use SWR provided solutions for infinite scrolling. You have different options documented here.
Plain fetch
In this case, you can also consider using directly the fetch function and appending data to the memes list directly:
const [ memes, setMemes ] = useState([]);
async function fetchAnotherPage() {
const data = (await fetch('https://meme-api.herokuapp.com/gimme/5')).json();
setMemes(value => [...value, ...data.memes]);
}
useEffect(() => fetchAnotherPage(), []);
I am very new to Reactjs, I am working on retrieving some data in order to display it, everything gets displayed however, when I filter there is an error that comes up "Cannot read property 'filter' of undefined", after debugging I found out that dataList is returning with undefined when typing anything in the search bar.
Appreciate your assistance.
function App() {
var dataList;
useEffect(() => {
// http get request
const headers = {
'Content-Type': 'application/json',
'Authorization': '***********************',
'UserAddressId': ****,
'StoreId': *
}
axios.get('https://app.markitworld.com/api/v2/user/products', {
headers: headers
})
.then((response) => {
dataList = response.data.data.products
setData(dataList)
})
.catch((error) => {
console.log(error)
})
}, []);
const [searchText, setSearchText] = useState([]);
const [data, setData] = useState(dataList);
// exclude column list from filter
const excludeColumns = ["id"];
// handle change event of search input
const handleChange = value => {
setSearchText(value);
filterData(value);
};
// filter records by search text
const filterData = (value) => {
console.log("dataList", dataList)
const lowercasedValue = value.toLowerCase().trim();
if (lowercasedValue === "") setData(dataList);
else {
const filteredData = dataList.filter(item => {
return Object.keys(item).some(key =>
excludeColumns.includes(key) ? false :
item[key].toString().toLowerCase().includes(lowercasedValue)
);
});
setData(filteredData);
}
}
return (
<div className="App">
Search: <input
style={{ marginLeft: 5 }}
type="text"
placeholder="Type to search..."
value={searchText}
onChange={e => handleChange(e.target.value)}
/>
<div className="box-container">
{data && data.length > 0 ? data.map((d, i) => {
return <div key={i} className="box">
<b>Title: </b>{d.title}<br />
<b>Brand Name: </b>{d.brand_name}<br />
<b>Price: </b>{d.price}<br />
<b>Status: </b>{d.status}<br />
</div>
}) : "Loading..."}
<div className="clearboth"></div>
{data && data.length === 0 && <span>No records found to display!</span>}
</div>
</div>
);
}
export default App;
You're mixing up a stateful data variable with a separate non-stateful, local dataList variable. The dataList only gets assigned to inside the axios.get, so it's not defined on subsequent renders; the setData(dataList) puts it into the stateful data, but the dataList on subsequent renders remains undefined.
To make things easier to understand, remove the dataList variable entirely, and just use the stateful data.
You also probably don't want to discard the existing data when the user types something in - instead, figure out what items should be displayed while rendering; rework the filterData so that its logic is only carried out while returning the JSX.
const [searchText, setSearchText] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
// http get request
const headers = {
'Content-Type': 'application/json',
'Authorization': '***********************',
'UserAddressId': ****,
'StoreId': *
}
axios.get('https://app.markitworld.com/api/v2/user/products', {
headers: headers
})
.then((response) => {
setData(response.data.data.products);
})
.catch((error) => {
console.log(error)
})
}, []);
// handle change event of search input
const handleChange = value => {
setSearchText(value);
};
// filter records by search text
const filterData = () => {
const lowercasedValue = searchText.toLowerCase().trim();
return lowercasedValue === ""
? data
: data.filter(
item => Object.keys(item).some(
key => excludeColumns.includes(key) ? false :
item[key].toString().toLowerCase().includes(lowercasedValue)
)
);
}
And change
{data && data.length > 0 ? data.map((d, i) => {
to
{filterData().map((d, i) => {
Your searchText should also be text, not an array: this
const [searchText, setSearchText] = useState([]);
should be
const [searchText, setSearchText] = useState('');
First of all, you don't need to maintain an additional non-state variable dataList as the local state data would serve the purpose.
API Call Code:
You should directly store the response from API after null checks satisfy.
useEffect(() => {
const headers = {
// key value pairs go here
};
// http request
axios.get(endPoint, {
headers,
})
.then((response) => {
// set data directly null checks
setData(response.data.data.products);
})
.catch((error) => {
console.log(error);
});
}, []);
Filteration Code
Use useCallback hook which would return a memoized version of the callback, unless the value of data changes.
const filterData = useCallback((value) => {
console.log('data', data);
// rest of code
}, [data]);
I am making dummy app to test server side API.
First request returns nested JSON object with Product names and number of variants that it has. From there I extract Product name so I can send second request to fetch list of variants with product images, sizes etc.
Sometimes it will load and display variants from only one product but most of the times it will work correctly and load all variants from both dummy products.
Is there a better way of doing this to ensure it works consistently good. Also I would like to know if there is a better overall approach to write something like this.
Here is the code:
import React, { useEffect, useState } from "react";
import axios from "axios";
import ShirtList from "../components/ShirtList";
const recipeId = "15f09b5f-7a5c-458e-9c41-f09d6485940e";
const HomePage = props => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
axios
.get(
`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
)
.then(response => {
let shirtList = [];
const itemsLength = response.data.Products.length;
response.data.Products.forEach((element, index) => {
axios
.get(
`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${element.ProductName}`
)
.then(response => {
shirtList.push(response.data.Variants);
if (index === itemsLength - 1) {
setLoaded(shirtList);
}
});
});
});
}, []);
const ListItems = props => {
if (props.loaded) {
return loaded.map(item => <ShirtList items={item} />);
} else {
return null;
}
};
return (
<div>
<ListItems loaded={loaded} />
</div>
);
};
export default HomePage;
You are setting the loaded shirts after each iteration so you will only get the last resolved promise data, instead fetch all the data and then update the state.
Also, separate your state, one for the loading state and one for the data.
Option 1 using async/await
const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'
const fetchProducts = async () => {
const { data } = await axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
return data.Products
}
const fetchShirts = async productName => {
const { data } = await axios.get(
`${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
)
return data.Variants
}
const HomePage = props => {
const [isLoading, setIsLoading] = useState(false)
const [shirtList, setShirtList] = useState([])
useEffect(() => {
setIsLoading(true)
const fetchProductShirts = async () => {
const products = await fetchProducts()
const shirts = await Promise.all(
products.map(({ productName }) => fetchShirts(productName)),
)
setShirtList(shirts)
setIsLoading(false)
}
fetchProductShirts().catch(console.log)
}, [])
}
Option 2 using raw promises
const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'
const fetchProducts = () =>
axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
.then(({ data }) => data.Products)
const fetchShirts = productName =>
axios
.get(
`${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
)
.then(({ data }) => data.Variants)
const HomePage = props => {
const [isLoading, setIsLoading] = useState(false)
const [shirtList, setShirtList] = useState([])
useEffect(() => {
setIsLoading(true)
fetchProducts
.then(products) =>
Promise.all(products.map(({ productName }) => fetchShirts(productName))),
)
.then(setShirtList)
.catch(console.log)
.finally(() => setIsLoading(false)
}, [])
}
Now you have isLoading state for the loading state and shirtList for the data, you can render based on that like this
return (
<div>
{isLoading ? (
<span>loading...</span>
) : (
// always set a unique key when rendering a list.
// also rethink the prop names
shirtList.map(shirt => <ShirtList key={shirt.id} items={shirt} />)
)}
</div>
)
Refferences
Promise.all
Promise.prototype.finally
React key prop
The following should pass a flat array of all variants (for all products ) into setLoaded. I think this is what you want.
Once all the products have been retrieved, we map them to an array of promises for fetching the variants.
We use Promise.allSettled to wait for all the variants to be retrieved, and then we flatten the result into a single array.
useEffect(()=>(async()=>{
const ps = await getProducts(recipeId)
const variants = takeSuccessful(
await Promise.allSettled(
ps.map(({ProductName})=>getVariants({ recipeId, ProductName }))))
setLoaded(variants.flat())
})())
...and you will need utility functions something like these:
const takeSuccessful = (settledResponses)=>settledResponses.map(({status, value})=>status === 'fulfilled' && value)
const productURL = (recipeId)=>`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
const variantsURL = ({recipeId, productName})=>`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`
const getProducts = async(recipeId)=>
(await axios.get(productURL(recipeId)))?.data?.Products
const getVariants = async({recipeId, productName})=>
(await axios.get(variantsURL({recipeId,productName})))?.data?.Variants
I'm newbie in React but I'm developing an app which loads some data from the server when user open the app. App.js render this AllEvents.js component:
const AllEvents = function ({ id, go, fetchedUser }) {
const [popout, setPopout] = useState(<ScreenSpinner className="preloader" size="large" />)
const [events, setEvents] = useState([])
const [searchQuery, setSearchQuery] = useState('')
const [pageNumber, setPageNumber] = useState(1)
useEvents(setEvents, setPopout) // get events on the main page
useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber)
// for ajax pagination
const handleSearch = (searchQuery) => {
setSearchQuery(searchQuery)
setPageNumber(1)
}
return(
<Panel id={id}>
<PanelHeader>Events around you</PanelHeader>
<FixedLayout vertical="top">
<Search onChange={handleSearch} />
</FixedLayout>
{popout}
{
<List id="event-list">
{
events.length > 0
?
events.map((event, i) => <EventListItem key={event.id} id={event.id} title={event.title} />)
:
<InfoMessages type="no-events" />
}
</List>
}
</Panel>
)
}
export default AllEvents
useEvents() is a custom hook in EventServerHooks.js file. EventServerHooks is designed for incapsulating different ajax requests. (Like a helper file to make AllEvents.js cleaner) Here it is:
function useEvents(setEvents, setPopout) {
useEffect(() => {
axios.get("https://server.ru/events")
.then(
(response) => {
console.log(response)
console.log(new Date())
setEvents(response.data.data)
setPopout(null)
},
(error) => {
console.log('Error while getting events: ' + error)
}
)
}, [])
return null
}
function useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber) {
useEffect(() => {
setPopout(<ScreenSpinner className="preloader" size="large" />)
let cancel
axios({
method: 'GET',
url: "https://server.ru/events",
params: {q: searchQuery, page: pageNumber},
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(
(response) => {
setEvents(response.data)
setPopout(null)
},
(error) => {
console.log('Error while getting events: ' + error)
}
).catch(
e => {
if (axios.isCancel(e)) return
}
)
return () => cancel()
}, [searchQuery, pageNumber])
return null
}
export { useEvents, useSearchedEvents }
And here is the small component InfoMessages from the first code listing, which display message "No results" if events array is empty:
const InfoMessages = props => {
switch (props.type) {
case 'no-events':
{console.log(new Date())}
return <Div className="no-events">No results :(</Div>
default:
return ''
}
}
export default InfoMessages
So my problem is that events periodically loads and periodically don't after app opened. As you can see in the code I put console log in useEvents() and in InfoMessages so when it's displayed it looks like this:
logs if events are displayed, and the app itself
And if it's not displayed it looks like this: logs if events are not displayed, and the app itself
I must note that data from the server is loaded perfectly in both cases, so I have totally no idea why it behaves differently with the same code. What am I missing?
Do not pass a hook to a custom hook: custom hooks are supposed to be decoupled from a specific component and possibly reused. In addition, your custom hooks return always null and that's wrong. But your code is pretty easy to fix.
In your main component you can fetch data with a custom hook and also get the loading state like this, for example:
function Events () {
const [events, loadingEvents] = useEvents([])
return loadingEvents ? <EventsSpinner /> : <div>{events.map(e => <Event key={e.id} title={e.title} />}</div>
}
In your custom hook you should return the internal state. For example:
function useEvents(initialState) {
const [events, setEvents] = useState(initialState)
const [loading, setLoading] = useState(true)
useEffect(function() {
axios.get("https://server.ru/events")
.then(
(res) => {
setEvents(res.data)
setLoading(false)
}
)
}, [])
return [events, loading]
}
In this example, the custom hook returns an array because we need two values, but you could also return an object with two key/value pairs. Or a simple variable (for example only the events array, if you didn't want the loading state), then use it like this:
const events = useEvents([])
This is another example that you can use, creating a custom hook that performs the task of fetching the information
export const useFetch = (_url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(true);
useEffect(function() {
setLoading('procesando...');
setData(null);
setError(null);
const source = axios.CancelToken.source();
setTimeout( () => {
axios.get( _url,{cancelToken: source.token})
.then(
(res) => {
setLoading(false);
console.log(res.data);
//setData(res);
res.data && setData(res.data);
// res.content && setData(res.content);
})
.catch(err =>{
setLoading(false);
setError('si un error ocurre...');
})
},1000)
return ()=>{
source.cancel();
}
}, [_url])
I'm trying to query data from the Prismic headless CMS API and running into problems using React Hooks. The prismic API is returning null, though I know its being passed down correctly as I can query it successfully without using react hooks.
Heres my current compontent code. Its returning "cannot read property 'api' of null". It doesn't reach the 'data' console log.
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
}, []);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
It seems to be a case where on initial render prismicCtx is null and only on the subsequent render you receive the updated value. The solution is obviously to call the effect on change of prismicCtx, but you if you just want to call the api on initial render you would need to keep track of whether you called the api earlier or not which you can achieve by using useRef and also you don't need to set the state as empty if prismicCtx doesn't exist
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
const isFirstCall = useRef(true);
useEffect(() => {
if(prismicCtx && isFirstCall.current) {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
isFirstCall.current = false;
}
},[prismicCtx]);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
Figured it out, I beleive. PrismicCTX was being changed up the tree so it was switching to undefinded. A simple if/else fixed it and making it so it only updated on that prop change. Still not sure if best practice though!
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(
() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
if (prismicCtx) {
fetchLinks();
} else {
setLinks([]);
}
},
[prismicCtx]
);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;