Get data from promise and use it in hook - reactjs

I want to make hook to get data from Snapshot to display proposals. I use graphql-request library to get data. I want to get this data in component for example: const { data } = useSnapshotProposalsQuery(). How can i do this? For now i can only get const data = useSnapshotProposalsQuery() and when i am console.log(data) i get Promise{<opening>}. My code:
import { gql, request } from 'graphql-request';
export const useSnapshotProposals = gql`
query Proposals {
proposals(
first: 20
skip: 0
where: { space_in: ["example.eth"] }
orderBy: "created"
orderDirection: desc
) {
id
title
body
choices
start
end
snapshot
state
author
space {
id
name
}
}
}
`;
export const useSnapshotProposalsQuery = () => {
return request('https://hub.snapshot.org/graphql', useSnapshotProposals).then((data) => data);
};

You create a custom hook. and that hook returns a state. when sideeffect inside that hook happens, the state is updated and your outer component gets re-rendered. (react docs)
export const useSnapshotProposalsQuery = () => {
const [myData, setMyData] = useState(null);
useEffect(()=>{
request('https://hub.snapshot.org/graphql', useSnapshotProposals).then((data) => {setMyData(data)});
}, []); // run only one time
return myData;
};
in outer component:
function ABCcomponent () {
const myData = useSnapshotProposalsQuery(); // it will be null at first, but will be filled with data later.
return (
/*ui that uses myData */
)
}

Related

Forcing a re-render of independent component using react-query

I have this problem.
I have defined two components. One for showing data, another one for searching. They are not connected together, but works independly. I use react-query for getting data from API.
Searchbox:
const SearchBox = () => {
const { data, status } = useQuery(['planets', searchTerm], () => fetchSearchingResults('planets', searchTerm), { enabled: Boolean(searchTerm)});
...
PlanetsList
const PlanetsList = () => {
const { data, status } = useQuery('planets', () => fetchResourceData('planets'));
**I change query data in SearchBox by searching and handling local state, it works. But I need call re-render of PlanetsList with data from SearchBox query result. How to do it? Is it possible **
You will need to call the same exact query in PlanetList.
Simple rerender won't help because useQuery(['planets', searchTerm]) and useQuery('planets') are completly different cache.
You could try to wrap your planets query in function if both queries gives the same type of result.
const usePlanetsQuery (searchTerm?: string) => {
return useQuery(['planets', searchTerm], () => searchTerm ? fetchSearchingResults('planets', searchTerm) : fetchResourceData('planets'));
}
const SearchBox = () => {
const searchTerm = selectSearchTempFromContextOrStateManageOrProps();
const { data, status } = usePlanetsQuery(searchTerm);
const PlanetsList = ({searchTerm}: {searchTerm: string}) => {
const { data, status } = usePlanetsQuery(searchTerm);
}
}
It will synchronize your results between SearchBox and PlanetsList

Context API values are being reset too late in the useEffect of the hook

I have a FilterContext provider and a hook useFilter in filtersContext.js:
import React, { useState, useEffect, useCallback } from 'react'
const FiltersContext = React.createContext({})
function FiltersProvider({ children }) {
const [filters, setFilters] = useState({})
return (
<FiltersContext.Provider
value={{
filters,
setFilters,
}}
>
{children}
</FiltersContext.Provider>
)
}
function useFilters(setPage) {
const context = React.useContext(FiltersContext)
if (context === undefined) {
throw new Error('useFilters must be used within a FiltersProvider')
}
const {
filters,
setFilters
} = context
useEffect(() => {
return () => {
console.log('reset the filters to an empty object')
setFilters({})
}
}, [setFilters])
{... do some additional stuff with filters if needed... not relevant }
return {
...context,
filtersForQuery: {
...filters
}
}
}
export { FiltersProvider, useFilters }
The App.js utilises the Provider as:
import React from 'react'
import { FiltersProvider } from '../filtersContext'
const App = React.memo(
({ children }) => {
...
...
return (
...
<FiltersProvider>
<RightSide flex={1} flexDirection={'column'}>
<Box flex={1}>
{children}
</Box>
</RightSide>
</FiltersProvider>
...
)
}
)
export default App
that is said, everything within FiltersProvider becomes the context of filters.
Now comes the problem description: I have selected on one page (Page1) the filter, but when I have to switch to another page (Page2), I need to flush the filters. This is done in the useFilters hook in the unmount using return in useEffect.
The problem is in the new page (Page2), during the first render I'm still getting the old values of filters, and than the GraphQL request is sent just after that. Afterwards the unmount of the hook happens and the second render of the new page (Page2) happens with set to empty object filters.
If anyone had a similar problem and had solved it?
first Page1.js:
const Page1 = () => {
....
const { filtersForQuery } = useFilters()
const { loading, error, data } = useQuery(GET_THINGS, {
variables: {
filter: filtersForQuery
}
})
....
}
second Page2.js:
const Page2 = () => {
....
const { filtersForQuery } = useFilters()
console.log('page 2')
const { loading, error, data } = useQuery(GET_THINGS, {
variables: {
filter: filtersForQuery
}
})
....
}
Printout after clicking from page 1 to page 2:
1. filters {isActive: {id: true}}
2. filters {isActive: {id: true}}
3. page 2
4. reset the filters to an empty object
5. 2 reset the filters to an empty object
6. filters {}
7. page 2
As I mentioned in the comment it might be related to the cache which I would assume you are using something like GraphQL Apollo. It has an option to disable cache for queries:
fetchPolicy: "no-cache",
By the way you can also do that reset process within the Page Two component if you want to:
const PageTwo = () => {
const context = useFilters();
useEffect(() => {
context.setFilters({});
}, [context]);
For those in struggle:
import React, { useState, useEffect, useCallback, **useRef** } from 'react'
const FiltersContext = React.createContext({})
function FiltersProvider({ children }) {
const [filters, setFilters] = useState({})
return (
<FiltersContext.Provider
value={{
filters,
setFilters,
}}
>
{children}
</FiltersContext.Provider>
)
}
function useFilters(setPage) {
const isInitialRender = useRef(true)
const context = React.useContext(FiltersContext)
if (context === undefined) {
throw new Error('useFilters must be used within a FiltersProvider')
}
const {
filters,
setFilters
} = context
useEffect(() => {
**isInitialRender.current = false**
return () => {
console.log('reset the filters to an empty object')
setFilters({})
}
}, [setFilters])
{... do some additional stuff with filters if needed... not relevant }
return {
...context,
filtersForQuery: { // <---- here the filtersForQuery is another variable than just filters. This I have omitted in the question. I will modify it.
**...(isInitialRender.current ? {} : filters)**
}
}
}
export { FiltersProvider, useFilters }
What is done here: set the useRef bool varialbe and set it to true, as long as it is true return always an empty object, as the first render happens and/or the setFilters function updates, set the isInitialRender.current to false. such that we return updated (not empty) filter object with the hook.

React using fetch returns undefined until save

new to react so I am not quite sure what I am doing wrong here... I am trying to call data from an API, then use this data to populate a charts.js based component. When I cmd + s, the API data is called in the console, but if I refresh I get 'Undefined'.
I know I am missing some key understanding about the useEffect hook here, but i just cant figure it out? All I want is to be able to access the array data in my component, so I can push the required values to an array... ive commented out my attempt at the for loop too..
Any advice would be greatly appreciated! My not so functional code below:
import React, {useState, useEffect} from 'react'
import {Pie} from 'react-chartjs-2'
const Piegraph = () => {
const [chartData, setChartData] = useState();
const [apiValue, setApiValue] = useState();
useEffect(async() => {
const response = await fetch('https://api.spacexdata.com/v4/launches/past');
const data = await response.json();
const item = data.results;
setApiValue(item);
chart();
},[]);
const chart = () => {
console.log(apiValue);
const success = [];
const failure = [];
// for(var i = 0; i < apiValue.length; i++){
// if(apiValue[i].success === true){
// success.push("success");
// } else if (apiValue[i].success === false){
// failure.push("failure");
// }
// }
var chartSuccess = success.length;
var chartFail = failure.length;
setChartData({
labels: ['Success', 'Fail'],
datasets: [
{
label: 'Space X Launch Statistics',
data: [chartSuccess, chartFail],
backgroundColor: ['rgba(75,192,192,0.6)'],
borderWidth: 4
}
]
})
}
return (
<div className="chart_item" >
<Pie data={chartData} />
</div>
);
}
export default Piegraph;
There are a couple issues that need sorting out here. First, you can't pass an async function directly to the useEffect hook. Instead, define the async function inside the hook's callback and call it immediately.
Next, chartData is entirely derived from the apiCall, so you can make that derived rather than being its own state variable.
import React, { useState, useEffect } from "react";
import { Pie } from "react-chartjs-2";
const Piegraph = () => {
const [apiValue, setApiValue] = useState([]);
useEffect(() => {
async function loadData() {
const response = await fetch(
"https://api.spacexdata.com/v4/launches/past"
);
const data = await response.json();
const item = data.results;
setApiValue(item);
}
loadData();
}, []);
const success = apiValue.filter((v) => v.success);
const failure = apiValue.filter((v) => !v.success);
const chartSuccess = success.length;
const chartFail = failure.length;
const chartData = {
labels: ["Success", "Fail"],
datasets: [
{
label: "Space X Launch Statistics",
data: [chartSuccess, chartFail],
backgroundColor: ["rgba(75,192,192,0.6)"],
borderWidth: 4,
},
],
};
return (
<div className="chart_item">
<Pie data={chartData} />
</div>
);
};
export default Piegraph;
pull your chart algorithm outside or send item in. Like this
useEffect(async() => {
...
// everything is good here
chart(item)
})
you might wonder why I need to send it in. Because inside useEffect, your apiValue isn't updated to the new value yet.
And if you put the console.log outside of chart().
console.log(apiData)
const chart = () => {
}
you'll get the value to be latest :) amazing ?
A quick explanation is that, the Piegraph is called whenever a state is updated. But this update happens a bit late in the next cycle. So the value won't be latest within useEffect.

Why does my UseState hook keeps on failing?

I want to use UseState hook for updating data in my Table component. The data to be used in the Table component is fetched by another function which is imported paginationForDataAdded.
Its look like stackoverflow due to re-rendering.
setAllData(searchResults); will re-render the component and again make api call and repated.
right way to call API.
const [allData, setAllData] = useState([]);
useEffect(function () {
const {
searchResults,
furnishedData,
entitledData
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
setAllData(searchResults);
});
Assuming paginationForDataAdded is a function that returns a Promise which resolves with an object that looks like the following:
{
searchResults: { resultarray: [...] },
furnishedData: [...],
entitledData: [...]
}
You should do the following your in component:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
paginationForDataAdded({
searchFunction: search,
collectionsData: collections,
})
.then(
({ searchResults, furnishedData, entitledData }) => {
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData);
}
)
.catch(/* handle errors appropriately */);
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
However, if paginationForDataAdded is not an asynchronous call, then you should do the following:
function App(props) {
const [allData, setAllData] = React.useState([]);
// ...
React.useEffect(() => {
const {
searchResults,
furnishedData,
entitledData,
} = paginationForDataAdded({
searchFunction: search,
collectionsData: collections
});
const nextAllData = searchResults.resultarray || [];
setAllData(nextAllData)
// an empty dependency array so that this hooks runs
// only once when the component renders for the first time
}, [])
return (
<Table
id="pop-table"
data={allData}
tableColumns={[...]}
/>
);
}
Hope this helps.

How to use multiple graphql query hooks in single function component in react?

I call multiple graphql query using useQuery. And it's shows error like
Too many re-renders. React limits the number of renders to prevent an infinite loop.
I know why it came but i don't know how to prevent from this error. This is Functional components
my code here
const [categoryList, updateCategoryList] = useState([]);
const [payeeList, updatePayeeList] = useState([]);
if (data) {
const categories = (data.categories as unknown) as Category[];
if (
!isEqual(categoryList, categories) ||
categoryList.length !== categories.length
) {
updateCategoryList([...categories]);
}
}
if (isEmpty(payeeList)) {
const { loading, data } = useQuery(payeesQuery);
if (data) {
const payees = (data.payees as unknown) as Payee[];
if (!isEqual(payeeList, payees) || payeeList.length !== payees.length) {
updateCategoryList([...payees]);
}
}
}
Sorry guys I noob for react.
for eg you can use like this
const ExampleComponent = () => {
const { loading:payeesLoading, data:payeesData } = useQuery(payeesQuery);
const { loading:secondLoading, data:secondData } = useQuery(secondQuery);
useEffect(() => { //effect hook for first query
} ,[payeesData] )
useEffect(() => { //effect hook for second query
} ,[secondData] )
return (<div>
your contents
</div>)
}
like this you can write multiple querys in a single component.

Resources