can i memoize custom hook which is graphql query in react? - reactjs

// useMe.tsx //
import { gql, useQuery } from "#apollo/client";
import { meQuery } from "../__generated__/meQuery";
export const ME_QUERY = gql`
query meQuery {
me {
id
email
my_workspaces {
title
}
}
}
`;
export const useMe = () => {
return useQuery<meQuery>(ME_QUERY, {
fetchPolicy: "cache-first"
});
};
This is my custom hook. I am trying to get user info with graphql query and made it to custom hook.
But there was some problem, whenever the high-level components state changed,
the low-level component which calling custom-hook(useMe) always rerendered
so i wanna memoize custom hook with useCallback or useMemo but i can't find right answer
please help me, Thank you

Just wrap the value with useCallback is fine
const useMeQuery = () => {
return useQuery<meQuery>(ME_QUERY, {
fetchPolicy: "cache-first"
});
export const useMe = useCallbak(useMeQuery)
}

Related

Invalid hook call. Hooks can only be called inside of the body of a function component when i call useQuery in useEffect

I am using apollo-graphql in my react project and i am getting error of
Invalid hook call. Hooks can only be called inside of the body of a function component
Here is my code for this
import React, { useEffect, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
// **************** COMPONENTS ****************
import { GET_MORTGAGE_JOURNEY } from "../../../../Graphql/Journeys/query";
export default function index() {
const insuranceId = useSelector((state) => state.mortgage.insuranceId);
// Panels Heading to show on all panels
useEffect(() => {
if (insuranceId) {
getMortgageData(insuranceId);
}
}, [insuranceId]);
function getMortgageData(insuranceId) {
const { loading, error, data } = useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
});
console.log(data);
}
return <section className="mortage-journey"></section>;
}
Once i run this i get the error, I know that useQuery itself is a hook and i cant call it from inside useEffect, but then what should be the workaround for this as i need insuranceId from my redux state first and send it to the query.
Thanks !
You are breaking the rule of hooks when you call it from any place other than the top level of a React component.
useEffect takes a callback function, and you are calling your hook from that. It is a problem.
I found this skip option in useQuery which helps you call useQuery conditionally.
useEffect(() => {
const { loading, error, data } =
useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
skip : (!insuranceId)
});
}, [insuranceId]);
Any time insuranceId changes your callback runs, so it is run after mount once and then on subsequent changes.
Try using refetch. Something like this:
const { data, refetch } = useQuery(MY_QUERY);
useEffect(() => {
refetch();
}, id);
You can use refetch wherever you like, in useEffect or a button click onclick handler.
Alternatively, you could use useLazyQuery if you don't want it to run the first time:
const [goFetch, { data }] = useLazyQuery(MY_QUERY);
Now you can use goFetch or whatever you want to call it wherever you like.
Your whole example might look like:
import React, { useEffect, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { GET_MORTGAGE_JOURNEY } from "../../../../Graphql/Journeys/query";
export default function index() {
const insuranceId = useSelector((state) => state.mortgage.insuranceId);
const { loading, error, data, refetch } = useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
});
useEffect(() => {
if (insuranceId) {
refetch({id: insuranceId});
}
}, [insuranceId]);
return <section className="mortage-journey"></section>;
}

Use `useQuery` hook the same way as `useMutation` fails

Using React and Apollo React hooks
import { useCallback, useState } from "react";
import { useMutation, useSubscription, useQuery } from "#apollo/react-hooks";
I got a function useChannel in which I have hooks with all my graphql mutations.
Each mutation is imported and then declared as a constant like so:
const [addExportChannel] = useMutation(AddExportChannelMutation);
Which I consume in the returned function of the hook like so
const onAddImportChannel = useCallback(
async (props) => {
try {
const data = await addImportChannel({
variables: {
shopId,
name: props.name,
url: props.url,
filetype: props.filetype
}
});
...
When I try to do the same with useQuery it fails saying useQuery is not iterable.
TypeError: useQuery is not a function or its return value is not iterable
const [FeedProcess] = useQuery(GetFeedProcessQuery);
...
const onFeedProcess = useCallback(
async (props) => {
try {
const data = await FeedProcess({
variables: { shopId, feedId: props.feedId }
});
...
What am I missing here, trying to figure out what is so different in the two hooks.
Because useQuery is called immediately you cannot store it like this (const [FeedProcess] = useQuery(GetFeedProcessQuery);). But apollo's got useLazyQuery that can be stored like you did. If you'd like to store and invoke it later, use useLazyQuery instead.
You should de-structure the props from useQuery hook like so:
const {data, loading, error} = useQuery(GetFeedProcessQuery);

React hooks useState getting diferrent value from redux state

I have react component look like this following code:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import { createClient, getClients } from "../redux/actions/clients";
function UpdateClient(props) {
let params = useParams();
const { error, successSubmit, clients } = useSelector(
(state) => state.clients
);
const [client, setClient] = useState(clients[0]);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getClients({ id: params.id }));
}, []);
const submitClient = () => {
dispatch(createClient(client));
};
return (
<div>{client.name} {clients[0].name}</div>
);
}
export default UpdateClient;
And the result is different client.name return test1,
while clients[0].name return correct data based on route parameter id (in this example parameter id value is 7) which is test7
I need the local state for temporary saving form data. I don't know .. why it's become different?
Can you please help me guys? Thanks in advance
You are referencing a stale state which is a copy of the clients state.
If you want to see an updated state you should use useEffect for that.
useEffect(() => {
setClient(clients[0]);
}, [clients]);
Notice that duplicating state is not recommended.
There should be a single “source of truth” for any data that changes in a React application.

pass data to a Layout component gatsbyJS

I want to pass data to layout Component, the data is from an API, cockpitCMS to be exact and the data is slug to be exact too.
io have tried this
import { useStaticQuery, graphql } from "gatsby"
export const slugs = () => {
const { data } = useStaticQuery(
graphql`
query slug{
allCockpitPost {
edges {
node {
title {
slug
}
}
}
}
}
`
)
return data.allCockpitPost.edges.node.title
}
but, I get this instead... React Hook "useStaticQuery" is called in function "slugs" which is neither a React function component or a custom React Hook function
maybe because we can't use usestaticQuery twice, and it is already been used in the SEO component.
Your problem here is the way you use hook. Basically you have some ways to use useStaticQuery or any hook in a function:
That function NEED to return a React Component.
Other wise, that should be a HOC.
Example code for first way:
import AnyComponent from '../some-where';
const MockComponent = (props) => {
const { mockData } = useStaticQuery(anyQuery);
return (
<AnyComponent mockData={mockData}>
)
}
export default MockComponent
Example code for second way:
const withMockHOC = (AnyOtherComponent) => (props) => {
const { mockData } = useStaticQuery(anyQuery);
return <AnyOtherComponent {...props} mockData={mockData} />;
};
Hope this help.

Can I replace context with hooks?

Is there a way with new react hooks API to replace a context data fetch?
If you need to load user profile and use it almost everywhere, first you create context and export it:
export const ProfileContext = React.createContext()
Then you import in top component, load data and use provider, like this:
import { ProfileContext } from 'src/shared/ProfileContext'
<ProfileContext.Provider
value={{ profile: profile, reloadProfile: reloadProfile }}
>
<Site />
</ProfileContext.Provider>
Then in some other components you import profile data like this:
import { ProfileContext } from 'src/shared/ProfileContext'
const context = useContext(profile);
But there is a way to export some function with hooks that will have state and share profile with any component that want to get data?
React provides a useContext hook to make use of Context, which has a signature like
const context = useContext(Context);
useContext accepts a context object (the value returned from
React.createContext) and returns the current context value, as given
by the nearest context provider for the given context.
When the provider updates, this Hook will trigger a rerender with the
latest context value.
You can make use of it in your component like
import { ProfileContext } from 'src/shared/ProfileContext'
const Site = () => {
const context = useContext(ProfileContext);
// make use of context values here
}
However if you want to make use of the same context in every component and don't want to import the ProfileContext everywhere you could simply write a custom hook like
import { ProfileContext } from 'src/shared/ProfileContext'
const useProfileContext = () => {
const context = useContext(ProfileContext);
return context;
}
and use it in the components like
const Site = () => {
const context = useProfileContext();
}
However as far a creating a hook which shares data among different component is concerned, Hooks have an instance of the data for them self and don'tshare it unless you make use of Context;
updated:
My previous answer was - You can use custom-hooks with useState for that purpose, but it was wrong because of this fact:
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
The right answer how to do it with useContext() provided #ShubhamKhatri
Now i use it like this.
Contexts.js - all context export from one place
export { ClickEventContextProvider,ClickEventContext} from '../contexts/ClickEventContext'
export { PopupContextProvider, PopupContext } from '../contexts/PopupContext'
export { ThemeContextProvider, ThemeContext } from '../contexts/ThemeContext'
export { ProfileContextProvider, ProfileContext } from '../contexts/ProfileContext'
export { WindowSizeContextProvider, WindowSizeContext } from '../contexts/WindowSizeContext'
ClickEventContext.js - one of context examples:
import React, { useState, useEffect } from 'react'
export const ClickEventContext = React.createContext(null)
export const ClickEventContextProvider = props => {
const [clickEvent, clickEventSet] = useState(false)
const handleClick = e => clickEventSet(e)
useEffect(() => {
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('click', handleClick)
}
}, [])
return (
<ClickEventContext.Provider value={{ clickEvent }}>
{props.children}
</ClickEventContext.Provider>
)
}
import and use:
import React, { useContext, useEffect } from 'react'
import { ClickEventContext } from 'shared/Contexts'
export function Modal({ show, children }) {
const { clickEvent } = useContext(ClickEventContext)
useEffect(() => {
console.log(clickEvent.target)
}, [clickEvent])
return <DivModal show={show}>{children}</DivModal>
}

Resources