useQuery used in custom hook returns a response with type useQueryResults<unknown, unknown> - javascript

I have the following custom hook called useGetQuery
type Props = Parameters<typeof useQuery>;
const useGetQuery = (...params: Props) => {
const useQueryResults = useQuery(params);
useFocusEffect(
React.useCallback(() => {
useQueryResults.refetch();
}, [useQueryResults])
);
return useQueryResults;
};
Which I then call in useGetScreen like this:
export const useGetScreen = (screenId: string) => {
return useGetQuery(['getScreen', { screenId }], () => getScreen(screenId), { staleTime: 0 });
};
And useGetScreen is called like this
const { data: screen } = useGetScreen('vegetables');
My problem is that in this case, article has a type of unknown and I can't figure out why is this happening. My guess is that I have to somehow type what is being returned by useFocusRefetchQuery. Before I implemented the custom hook, Typescript automatically inferred the return type of useQuery

I don't recall typescript infering anything from the GQL request string, in my experience you need to provide useQuery the expected output and input (variables) types as mentioned here in details. The useQuery signature is as follow:
useQuery<TData = any, TVariables = OperationVariables>
In your case you would have to add both Props and your article type, say Article:
import { gql, QueryHookOptions, useQuery } from '#apollo/client';
const GET_ARTICLE_QUERY = gql`
query getArticle {
article: {
id,
articleId,
articleName,
}
}
`
interface ArticleContent {
__typename: "Article";
id: string;
articleId: string;
articleName: string;
}
interface Article {
article: ArticleContent | null;
}
interface ArticleInput {
articleId: string;
}
const useArticleQuery = (options: QueryHookOptions = {}) => {
return useQuery<Article, ArticleInput>(GET_ARTICLE_QUERY);
}
I think its also a good idea to keep the options (QueryHookOptions) in here so you can use your hook in different contexts.
You can use a tool like Graphql code generator to automatically generate the static types Article and ArticleInput for you (based on your schema and your JS gql requests).
If you want to have a generic hook for several useQuery that all look the same (for example they all use useFocusEffect), you will have to do something like this:
function useFocusRefetchQuery<TData, TVariables>(){
const useQueryResults = useQuery<TData, TVariables>();
// ...
}
This way you can use useFocusRefetchQuery instead of useQuery, but I really think you will have to pass both the input/output types, which means in the previous example you would have:
const useArticleQuery = (options: QueryHookOptions = {}) => {
return useFocusRefetchQuery<Article, ArticleInput>(GET_ARTICLE_QUERY);
}

Related

What is the best way to set react-hook-form defaultValues in this case?

I have a statically generated Next.js site with frontend only. I have a page pages/feedback/[id]/edit.tsx. Getting this dynamic id in a nested FeedbackForm component & setting defaultValues like so:
export const FeedbackForm = ({ editing }: { editing: boolean }) => {
const router = useRouter()
const { id } = router.query
const feedbackData = getFeedback(id as string)
const { register, handleSubmit } = useForm({
defaultValues: {
title: editing ? feedbackData.title : '',
category: editing ? feedbackData.category : categories[0], // an array from local json file
status: editing ? feedbackData.status : statusList[0], // an array from local json file
description: editing ? feedbackData.description : '',
}
})
// ...
}
The problem is that, initially, the id from router.query is undefined. As I've read in Next.js docs, client side router will take over after hydration and id will be filled with the value. This means that FeedbackForm component renders twice and, initially, getFeedbackData returns undefined (because undefined id was passed as an argument).
So my question is, what is the best way to set defaultValues in this case? Should I even use defaultValues here?
Should I modify getFeedbackData function to return object with empty string values if undefined was passed in?
Should I subscribe to the router object changes and only then fill the form with default values? I saw this being done with reset function in useEffect.
I presume that you use Next.JS from the tags.
Using useEffect and getServerSideProps or getStaticProps can be the solutions.
It depends on what you want to achieve.
If you need SEO, for instance, use getServerSideProps or getStaticProps. If you need a lot of dynamic data on your page, it's more scalable to render your page at run time (SSR), and therefore getServerSideProps would be the preferred method. (just keep in mind that the SEO is not the only reason why you would choose one over another)
Otherwise, useEffect will be enough for your needs.
The following codes are just examples, but you'll get the ideas.
using useEffect
interface FeedbackFormProps {
editing: boolean;
}
type FeedbackData = {
title: string;
category: string;
status: string;
description: string;
};
const FeedbackForm: React.FC<FeedbackFormProps> = ({ editing }) => {
const router = useRouter();
const [feedbackData, setFeedbackData] = useState<FeedbackData | null>(null);
const { register, handleSubmit } = useForm({
defaultValues: {
title: editing && feedbackData?.title ? feedbackData.title : '' ,
category: editing && feedbackData?.category ? feedbackData?.category : categories[0], // an array from local json file
...
}
})
useEffect(() => {
const { id } = router.query;
const fetchFeedback = async () => {
const result = await getFeedback(id as string);
setFeedbackData(result);
};
fetchFeedback();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!feedbackData) return <div>{/* loading spinner etc. */}</div>;
return <div>{/* do something with data */}</div>;
};
export default FeedbackForm;
using getServerSideProps
interface FeedbackFormProps {
feedbackData: FeedbackData;
editing: boolean;
}
type FeedbackData = {
title: string;
category: string;
status: string;
description: string;
};
const FeedbackForm: React.FC<FeedbackFormProps> = ({editing, feedbackData}) => {
const router = useRouter();
const { register, handleSubmit } = useForm({
defaultValues: {
title: editing && feedbackData?.title ? feedbackData.title : '' ,
category: editing && feedbackData?.category ? feedbackData?.category : categories[0], // an array from local json file
...
}
})
/* you can use setState to save the feedbackData and use it for later */
/* because if it's not editing mode, you might have to refetch the data */
if (!feedbackData) return <div>{/* loading spinner etc. */}</div>;
return <div>{/* do something with data */}</div>;
};
export default FeedbackForm;
export const getServerSideProps = async (context: NextPageContext) => {
const { id } = context.query;
const feedbackData = await getFeedback(id as string);
return {
props: {
feedbackData: feedbackData,
},
};
};
TMI: I assume the editing mode is able to toggle, so it might be better to save data using useState even in the case of #2 too.
In conclusion, since the editing prop from the parent mainly decides the default value, I recommend using useEffect

How to declare mapped property name in React Typescript App

I am trying to figure out why my code is telling me that my mapped data that has a property of 'name' is of type never. I am already declaring the commentsData in the interface but there is obviously an error occuring there.
Please see my code below:
interface IProps {
commentsData: []
}
const CommentList: React.FC<IProps> = ({ commentsData }: IProps) => {
return (
<div>
{commentsData.map(comment => {
return (
<div>
{comment.name}
</div>
)
})}
</div>
)
}
export const getStaticProps = async ()=> {
const data = await fetch('https://jsonplaceholder.typicode.com/comments')
.then((res) => res.json())
.then((data) => data);
return {
props: {
commentsData: data
}
}
}
export default CommentList;
And here is the image with the error that Typescript is throwing:
Anyone here ever saw this error? And how did you solve it. Thanks for any help!
You need to specify what the array is of. Something like:
interface IComment {
postId: number;
id: number;
name: string;
email: string;
body: string;
}
interface IProps {
commentsData: IComment[]
}
This way, TypeScript knows what kind of object to expect inside the array.
Well, you have to define an interface with an indexer. because it's look like an array of objects.
I am sharing the link which can help you. How can i define an interface for array of an object

Destructuring react hook results in: This expression is not callable

I'm creating a custom hook and want to return an object and two functions when the hook is called. I do not want to return it as return {body, setProperty, setBody}, since I might call the hook multiple times within the same component and need different names for the variables.
I'd want to call it just as useState where I can destructure it as an array const [firstBody, setFirstBodyProp, setFirstBody] = useJSONState({/*Some code*/}), but when I try to return it as such return [body, setProperty, setBody], I get the following error when calling it from a component:
This expression is not callable.
Type 'jsonType' has no call signatures.ts(2349)
My Code:
type jsonType = {
[property: string]: any
}
const useJSONState = (json: jsonType) => {
const [ body, setBody ] = useState(json)
function setProp(property: string, value: any){
let newBody = {...body}
newBody[property] = value
setBody(newBody)
}
return [body, setProp, setBody]
}
export default useJSONState
The reason for the error is TypeScript inference. Your hook is returning an array of body,setProp,setBody but TypeScript infers the type to be jsonType[], which is the type of body(the first element in the array). To solve this error, you have to specify the return type of the hook explicitly.
export const useJSONState = (
json: jsonType
): [
jsonType,
(property: string, value: any) => void,
React.Dispatch<React.SetStateAction<jsonType>>
] => {
// .....
return [body, setProp, setBody];
}

What is the correct to create a interface for action object with react hooks and typescript

I am working with react hooks and typescript. I used useReducer() for global state. The action of the reducer function contains two properties name and data. name means the name of event or change and data will be particular data required for that particular name.
There are four value for name till now. If name "setUserData" then data should IUserData(interface). If name is setDialog then data should DialogNames(type containing two strings). And if its something else then data is not required.
//different names of dialog.
export type DialogNames = "RegisterFormDialog" | "LoginFormDialog" | "";
//type for name property in action object
type GlobalStateActionNames =
| "startLoading"
| "stopLoading"
| "setUserData"
| "setDialog";
//interface for main global state object.
export interface IGlobalState {
loading: boolean;
userData: IUserData;
dialog: DialogNames;
}
interface IUserData {
loggedIn: boolean;
name: string;
}
//The initial global state
export const initialGlobalState: IGlobalState = {
loading: false,
userData: { loggedIn: false, name: "" },
dialog: ""
};
//The reducer function which is used in `App` component.
export const GlobalStateReducer = (
state: IGlobalState,
{ name, data }: IGlobalStateAction
): IGlobalState => {
switch (name) {
case "startLoading":
return { ...state, loading: true };
case "stopLoading":
return { ...state, loading: false };
case "setUserData":
return { ...state, userData: { ...state.userData, ...data } };
case "setDialog":
return { ...state, dialog: data };
default:
return state;
}
};
//The interface object which is passed from GlobalContext.Provider as "value"
export interface GlobalContextState {
globalState: IGlobalState;
dispatchGlobal: React.Dispatch<IGlobalStateAction<GlobalStateActionNames>>;
}
//intital state which is passed to `createContext`
export const initialGlobalContextState: GlobalContextState = {
globalState: initialGlobalState,
dispatchGlobal: function(){}
};
//The main function which set the type of data based on the generic type passed.
export interface IGlobalStateAction<
N extends GlobalStateActionNames = GlobalStateActionNames
> {
data?: N extends "setUserData"
? IUserData
: N extends "setDialog"
? DialogNames
: any;
name: N;
}
export const GlobalContext = React.createContext(initialGlobalContextState);
My <App> component looks like.
const App: React.SFC = () => {
const [globalState, dispatch] = React.useReducer(
GlobalStateReducer,
initialGlobalState
);
return (
<GlobalContext.Provider
value={{
globalState,
dispatchGlobal: dispatch
}}
>
<Child></Child>
</GlobalContext.Provider>
);
};
The above approach is fine. I have to use it like below in <Child>
dispatchGlobal({
name: "setUserData",
data: { loggedIn: false }
} as IGlobalStateAction<"setUserData">);
The problem is above approach is that it makes code a little longer. And second problem is I have to import IGlobalStateAction for not reason where ever I have to use dispatchGlobal
Is there a way that I could only tell name and data is automatically assigned to correct type or any other better way. Kindly guide to to the correct path.
Using useReducer with typescript is a bit tricky, because as you've mentioned the parameters for reducer vary depending on which action you take.
I came up with a pattern where you use classes to implement your actions. This allows you to pass typesafe parameters into the class' constructor and still use the class' superclass as the type for the reducer's parameter. Sounds probably more complicated than it is, here's an example:
interface Action<StateType> {
execute(state: StateType): StateType;
}
// Your global state
type MyState = {
loading: boolean;
message: string;
};
class SetLoadingAction implements Action<MyState> {
// this is where you define the parameter types of the action
constructor(private loading: boolean) {}
execute(currentState: MyState) {
return {
...currentState,
// this is how you use the parameters
loading: this.loading
};
}
}
Because the state update logic is now encapsulated into the class' execute method, the reducer is now only this small:
const myStateReducer = (state: MyState, action: Action<MyState>) => action.execute(state);
A component using this reducer might look like this:
const Test: FunctionComponent = () => {
const [state, dispatch] = useReducer(myStateReducer, initialState);
return (
<div>
Loading: {state.loading}
<button onClick={() => dispatch(new SetLoadingAction(true))}>Set Loading to true</button>
<button onClick={() => dispatch(new SetLoadingAction(false))}>Set Loading to false</button>
</div>
);
}
If you use this pattern your actions encapsulate the state update logic in their execute method, which (in my opinion) scales better, as you don't get a reducer with a huge switch-case. You are also completely typesafe as the input parameter's types are defined by the action's constructor and the reducer can simply take any implementation of the Action interface.

Calling typescript interface from commonJS function call

I have this code sample in ts AuthorityProvider is an interface its implemented on the library like this
export interface AuthorityProvider {
/** Get subset of `availableKeys` needed to meet authorities in `transaction` */
getRequiredKeys: (args: AuthorityProviderArgs) => Promise<string[]>;
}
TScode
const authorityProvider: AuthorityProvider = {
getRequiredKeys: (args: AuthorityProviderArgs): Promise<string[]> => {
return Promise.resolve([accountPublicKey])
},
}
I tried to recode this on CommonJS but no luck please advice
this is my code
const authorityProvider = ( getRequiredKeys => {
return Promise.resolve([accountPublicKey])
})

Categories

Resources