I'm using Typescript, and I know how to use useQuery hook with variables, but now I have a GraphQL query without variables like below:
const GetTopAlertsQuery = gql`
query getTopAlerts {
getTopAlerts {
ens
walletAddress
}
}
`;
Basically I just need it return all the data in the database without doing any filtering. I have already implemented the back-end and it works successfully, so the query should be good.
I also have set up these two interfaces to hold the data:
interface topAlertValue {
ens: string;
walletAddress: string;
}
interface jsonData {
topalerts: topAlertValue[];
}
And I have tried the below ways, but none of them work:
// attempt #1
const { data } = useQuery<jsonData>(
GetTopAlertsQuery
);
// attempt #2
const data = ({ topalerts }: jsonData ) => {
useQuery(GetTopAlertsQuery);
};
// attempt #3
const data = <Query<Data, Variables> query={GetTopAlertsQuery}>
{({ loading, error, data }) => { ... }}
</Query>
If you know how to use useQuery hook without variables, please help me out! Thanks!
Related
I have a static data structure that I'm serving from my Next project's API route which is an array of objects with the following interface:
export interface Case {
id: string;
title: string;
beteiligte: string[];
gerichtstermin?: string;
tasks?: [];
}
I'm using React Query to pull in the data in a component:
const Cases:React.FC = () => {
const { data, status } = useQuery("cases", fetchCases);
async function fetchCases() {
const res = await fetch("/api/dummy-data");
return res.json();
}
return (
<DefaultLayout>
<Loading loading={status === "loading"} />
{data.cases.map((case)=><p>{case.name}</p>)}
</DefaultLayout>
);
}
export default Cases;
But when trying to map over the data I keep getting these squiggly lines and an incredibly ambiguous error:
I've tried absolutely everything I can think of and have searched far and wide through React Query documentation, typescript guides and of course many, many stackoverflow posts. Please help! (typescript newbie)
UPDATE:
When I type {data.cases.map(()=>console.log("hello")} there is no error at all. But as soon as I add a parameter to the anonymous function, the errors pop up: {data.cases.map((case)=>console.log("hello"))}
In the case interface, you do not have "name" property.
you should be mapping only if success is true. you have to guard your code. see what you get console.log(data). also, use key prop when mapping
{status === "success" && (
// case is reserved word. use a different name
{data.cases.map((caseItem) => (
<p key={caseItem.id}>{caseItem.name}</p>
))}
)}
case is a reserved word. If you use any other word it works
I tried to reproduce your issue in sandbox and I managed to make it "transpile"
Sandbox link
You have to add prop name of type string to the Case interface in order to use it
export interface Case {
name: string;
id: string;
title: string;
beteiligte: string[];
gerichtstermin?: string;
tasks?: [];
}
// in my example interface is named CaseResonse
I assume your API response looks like this
{ "data": { "cases": [] } }
You have to define the API response to be usable:
interface GetCaseResponseWrapper {
cases: Array<Case>;
}
// in my example it is array of CaseResponse
interface GetCaseResponseWrapper {
cases: Array<CaseResponse>;
}
You need to decorate your fetchCases to return GetCaseResponseWrapper
async function fetchCases() {
const res = await fetch("/api/dummy-data");
const jsonResult = await res.json();
return jsonResult.data as GetCaseResponseWrapper;
}
the data from the useQuery may be undefined, so you should use ?.
{data?.cases.map((c) => (
<p>{c.name}</p>
))}
Sandbox link
I've been following along the REDUX essentials guide and I'm at part 8, combining RTK Query with the createEntityAdapter. I'm using the guide to implement it in a personal project where my getUni endpoint has an argument named country, as you can see from the code snippet below.
I'm wondering is there anyway to access the country argument value from the state in universityAdaptor.getSelector(state => ) at the bottom of the snippet, as the query key name keeps changing.
import {
createEntityAdapter,
createSelector,
nanoid
} from "#reduxjs/toolkit";
import {
apiSlice
} from "../api/apiSlice";
const universityAdapter = createEntityAdapter({})
const initialState = universityAdapter.getInitialState();
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUni: builder.query({
query: country => ({
url: `http://universities.hipolabs.com/search?country=${country}`,
}),
transformResponse: responseData => {
let resConvert = responseData.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(each => {
return { ...each,
id: nanoid()
}
});
return universityAdapter.setAll(initialState, resConvert)
}
})
})
});
export const {
useGetUniQuery
} = extendedApiSlice;
export const {
selectAll: getAllUniversity
} = universityAdapter.getSelectors(state => {
return Object.keys({ ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }).length === 0
? initialState : { ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }
})
UPDATE: I got it working with a turnery operator due to the multiple redux Actions created when RTK Query handles fetching. Wondering if this is best practice as I still haven't figured out how to access the country argument.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
return !Object.values(state.api.queries)[0]
? initialState : Object.values(state.api.queries)[0].status !== 'fulfilled'
? initialState : Object.values(state.api.queries)[0].data
})
I wrote that "Essentials" tutorial :)
I'm actually a bit confused what your question is - can you clarify what specifically you're trying to do?
That said, I'll try to offer some hopefully relevant info.
First, you don't need to manually call someEndpoint.select() most of the time - instead, call const { data } = useGetThingQuery("someArg"), and RTKQ will fetch and return it. You only need to call someEndpoint.select() if you're manually constructing a selector for use elsewhere.
Second, if you are manually trying to construct a selector, keep in mind that the point of someEndpoint.select() is to construct "a selector that gives you back the entire cache entry for that cache key". What you usually want from that cache entry is just the received value, which is stored as cacheEntry.data, and in this case that will contain the normalized { ids : [], entities: {} } lookup table you returned from transformResponse().
Notionally, you might be able to do something like this:
const selectNormalizedPokemonData = someApi.endpoints.getAllPokemon.select();
// These selectors expect the entity state as an arg,
// not the entire Redux root state:
// https://redux-toolkit.js.org/api/createEntityAdapter#selector-functions
const localizedPokemonSelectors = pokemonAdapter.getSelectors();
const selectPokemonEntryById = createSelector(
selectNormalizedPokemonData ,
(state, pokemonId) => pokemonId,
(pokemonData, pokemonId) => {
return localizedPokemonSelectors.selectById(pokemonData, pokemonId);
}
)
Some more info that may help see what's happening with the code in the Essentials tutorial, background - getLists endpoint takes 1 parameter, select in the service:
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId) : [];
};
And my selector in the slice:
export const selectAllLists = createSelector(getListsResult, (listsResult) => {
console.log('inside of selectAllLists selector = ', listsResult);
return listsResult.data;
// return useSelector(listsResult) ?? [];
});
Now this console logs listsResult as ƒ memoized() { function! Not something that can have .data property as tutorial suggests. Additionally return useSelector(listsResult) - makes it work, by executing the memoized function.
This is how far I got, but from what I understand, the code in the Essentials tutorial does not work as it is...
However going here https://codesandbox.io/s/distracted-chandrasekhar-r4mcn1?file=/src/features/users/usersSlice.js and adding same console log:
const selectUsersData = createSelector(selectUsersResult, (usersResult) => {
console.log("usersResult", usersResult);
return usersResult.data;
});
Shows it is not returning a memorised function, but an object with data on it instead.
Wonder if the difference happening because I have a parameter on my endpoint...
select returns a memoized curry function. Thus, call it with first with corresponding arg aka tribeId in your case and then with state. This will give you the result object back for corresponding chained selectors.
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId)(state) : [];
};
The intention of the getUni endpoint was to produce an array of university data. To implement the .getSelector function to retrieve that array, I looped over all query values, searching for a getUni query and ensuring they were fulfilled. The bottom turnery operator confirms the getUni endpoint was fired at least once otherwise, it returns the initialState value.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
let newObj = {};
for (const value of Object.values(state.api.queries)) {
if (value?.endpointName === 'getUni' && value?.status === 'fulfilled') {
newObj = value.data;
}
}
return !Object.values(newObj)[0] ? initialState : newObj;
})
I query a specific variable called nameTranslated from my schema, It takes the parameter of the locale with is En-CA, Fr-FA etc and gets the desired word in french. And the way I handle this in my frontend reactjs application is like this:
export const App = () => {
const { locale } = useIntl()
const LOAD_TABLE = gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${l}")
defaultClass {
nameTranslate(language:"${l}")
}
}
}
`
useEffect(() => {
// a function to fetch LOAD_TABLE
},[locale])
}
The above code works perfectly fine and whenever I change the locale variable it re fetches the query. But the problem with this is i have many other query I need to work with, my file length becomes too long and hard to manage. At the same time if I pull the file out, I lose the privilage of dynamacally adding a type for nameTranslate.. How can I solve this issue?
You can make it more modular but still dynamic by using custom hooks, for example:
// hooks/useItemsQuery.js
function useItemsQuery(locale) {
const itemsQuery = useMemo(() => gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`, [locale])
return itemsQuery
}
// App.js
export const App = () => {
const { locale } = useIntl()
const itemsQuery = useItemsQuery(locale)
useEffect(() => {
// a function to fetch itemsQuery
}, [itemsQuery])
}
Or if you need to call outside of React just a normal function will do. I think this won't have a perf impact as es6 tpl literals are cached (the gql`` part) as long as the variables don't change, even inside a function. If that's the case the use of useMemo above is redundant anyway.
function itemsQuery(locale) {
return gql`
query getItems($id: String!) {
Items(id: $id) {
id
notes
nameTranslate(language:"${locale}")
defaultClass {
nameTranslate(language:"${locale}")
}
}
}
`
}
I have started getting this warning, when I update an "Transaction" with a mutation.
The code is old and have newer showed this warning before. I do not know when this regression started.
Oddly enough, In my mind there should be no cache either, since fetchPolicy: "network-only" is set.
How can I get rid of the warning?
invariant.esm.js:42 Cache data may be lost when replacing the transactions field of a Query object.
To address this problem (which is not a bug in Apollo Client),
define a custom merge function for the Query.transactions field,
so InMemoryCache can safely merge these objects:
existing: [{"__ref":"Transaction:5feabda25e7967001267ffd2"},
{"__ref":"Transaction:5feabda55e7967001267ffd3"},
{"__ref":"Transaction:5feabda75e7967001267ffd4"},
{"__ref":"Transaction:5feabda95e7967001267ffd5"},
{"__ref":"Transaction:5feabdab5e7967001267ffd6"},
{"__ref":"Transaction:60127209c3731400116fe0c5"},
{"__ref":"Transaction:602543cde12cd00011881a8b"},
{"__ref":"Transaction:602544e7e12cd00011881a91"},
{"__ref":"Transaction:602f7d9be14be20011a5cbec"},
{"__ref":"Transaction:6033b4d5ad34870011e7ba08"},
{"__ref":"Transaction:603494fbad34870011e7bf07"},
{"__ref":"Transaction:6038a0519d844c00116e544f"},
{"__ref":"Transaction:6038a05f9d844c00116e545e"},
{"__ref":"Transaction:6038a06c9d844c00116e549e"},
{"__ref":"Transaction:6038a06f9d844c00116e54a1"},
{"__ref":"Transaction:6038a0549d844c00116e5452"},
{"__ref":"Transaction:6038a0629d844c00116e545f"},
{"__ref":"Transaction:6038a0699d844c00116e549a"},
{"__ref":"Transaction:603cd5c39d844c00116f232b"},
{"__ref":"Transaction:603ce07d9d844c00116f2342"},
{"__ref":"Transacti
incoming: [{"__ref":"Transaction:5feabda25e7967001267ffd2"},
{"__ref":"Transaction:5feabda55e7967001267ffd3"},
{"__ref":"Transaction:5feabda75e7967001267ffd4"},
{"__ref":"Transaction:5feabda95e7967001267ffd5"},
{"__ref":"Transaction:5feabdab5e7967001267ffd6"},
{"__ref":"Transaction:60127209c3731400116fe0c5"},
{"__ref":"Transaction:602543cde12cd00011881a8b"},
{"__ref":"Transaction:602544e7e12cd00011881a91"},
{"__ref":"Transaction:602f7d9be14be20011a5cbec"},
{"__ref":"Transaction:6033b4d5ad34870011e7ba08"},
{"__ref":"Transaction:603494fbad34870011e7bf07"},
{"__ref":"Transaction:6038a0519d844c00116e544f"},
{"__ref":"Transaction:6038a05f9d844c00116e545e"},
{"__ref":"Transaction:6038a06c9d844c00116e549e"},
{"__ref":"Transaction:6038a06f9d844c00116e54a1"},
{"__ref":"Transaction:6038a0549d844c00116e5452"},
{"__ref":"Transaction:6038a0629d844c00116e545f"},
{"__ref":"Transaction:6038a0699d844c00116e549a"},
{"__ref":"Transaction:603cd5c39d844c00116f232b"},
{"__ref":"Transaction:603ce07d9d844c00116f2342"},
{"__ref":"Transacti
For more information about these options, please refer to the documentation:
* Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers
* Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects
From code that looks like this:
const UPDATE_TRANSACTION = gql`
mutation updateTransaction($input: UpdateTransactionInput!) {
updateTransaction(input: $input) {
_id
status
}
}
`;
export const DealBlotterGrid = ({ startDate }: DealBlotterGridPropsType): ReactElement => {
const swedenIsoString = moment.tz(startDate, "Europe/Stockholm").format();
const { loading, error, data } = useQuery(GET_TRANSACTIONS, {
variables: { tradeTimestampStart: swedenIsoString },
fetchPolicy: "network-only",
pollInterval: 10000
});
const [updateTransactionStatus] = useMutation(UPDATE_TRANSACTION, {
refetchQueries: [
{
query: GET_TRANSACTIONS,
variables: { tradeTimestampStart: swedenIsoString }
}
]
});
...
I need to use 2 queries in my file and I am writing them like so:
const {loading, data } = useQuery(getCharactersQuery);
const {loading, data} = useQuery(getSingleCharacterQuery);
The problem is, they both have the same "loading" and "data" variables and I don't see anywhere in the docs how can we have different ones. How can I differentiate them?
It's Object destructuring of JS Destructuring assignment. You can choose not to use it here to give different variable names.
const resCharacters = useQuery(getCharactersQuery);
const resSingleCharacter = useQuery(getSingleCharacterQuery);
if (resCharacters.loading || resSingleCharacter.loading) return 'Loading...';
...
Ref: Apollo document of useQuery
This way, by giving them an alias.
const {loading, data } = useQuery(getCharactersQuery);
const {loading: singleCharacterLoading, data: singleCharacterData} = useQuery(getSingleCharacterQuery);
const GET_DATA = gql`
query {
characters {
phone
rating
ratingType
review
city
id
}
singleCharacter {
title
text
}
}
`;
const {loading, data } = useQuery(GET_DATA);
console.log(data) //{characters: [...], singleCharacter: [...]}