NextJS getStaticProps with Typescript - javascript

I'm following a NextJS course, however I want to use Typescript - I've read a github discussion forum, but cannot seem to understand why there is an error on the 1st function below, but not the second?
export async function getStaticProps(): GetStaticProps {
return {
props: {
example: ""
}
}
}
The error I get for the above function is the following:
The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise<GetStaticProps<{ [key: string]: any; }, ParsedUrlQuery, PreviewData>>'?
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
example: ""
}
}
};
However I do not get this error for the function above, why is that? What's the difference I thought these 2 functions are the same by definition, one is an arrow function while the other is a regular function?
Can someone please explain how I can use the first function with the appropriate TypeScript types? What's going on?

In the first function, your type specification is of the return type of the function. In the second function, your type specification is of the type of the function itself.
For example, these 2 functions are of the same type, but where you specify the type matters.
const hi: () => string = () => {
return "a"
}
const hi2 = (): string => {
return "a"
}
To answer your second question, all async functions must have return a Promise. As your first function returned GetStaticProps which is not a promise, it gives an error. The way to fix it would be to specify the return type as Promise<GetStaticProps> instead.

Related

Define a function that returns a function that returns void

I don't understand why this gives an error
function foo(): () => string {
return () => 123;
}
But this does not
function foo(): () => void {
return () => 123;
}
And also this will not give an error as well
function foo(): (a: number) => string {
return () => '123';
}
I explicitly type that returned function should accept one argument, then return a function that does not accept any, but TS does not give me any error.
Assigning a function of type () => number to something of type () => string clearly is a type error. However, for void as a return type, TypeScript is more lenient. From the handbook on the Assignability of Functions:
The void return type for functions can produce some unusual, but
expected behavior.
Contextual typing with a return type of void does not force functions
to not return something. Another way to say this is a contextual
function type with a void return type (type vf = () => void), when
implemented, can return any other value, but it will be ignored.
[…]
There is one other special case to be aware of, when a literal
function definition has a void return type, that function must not
return anything.
There's also an FAQ entry about it.
My investigations ended up with a fact that it is impossible to do this with TypesScript, unfortunately.
Here are the links that explain why this unintuitive decision was made
Void type
Assignability of functions
FAQ
The solution that is closest to the desired functionality is to
Type returned function to return undefined
function foo(): () => undefined { ... }
Set on option in tsconfig.json
"compilerOptions": {"noImplicitReturns": false}
Explicitly return from the returned function
function foo(): () => undefined {
return () => { return; }
}
Looks a bit ugly but now we can define a function that for sure will return nothing.

generic type for fucntion from test-library

I have part of code like following, but fail on type checking for error Don't use 'Function' as a type. The 'Function' type accepts any function-like value.. what is the best way for me to provide the type for the function from testing-library/react?
async function onAction(
queryByTestId: Function,
findByTestId: Function,
): Promise<void> {
await waitFor(() => expect(queryByTestId(selector)).toBeVisible());
const triggerChange = await findByTestId('NAME');
}
const {findByTestId, queryByTestId} = render(<Component />);
await onAction(queryByTestId, findByTestId);
The Function type is a very broad type, similar to how large the Object type is. It is recommended to define what the function definition will look like.
From the amount of code you have provided something like this might surfice:
async function onAction(
queryByTestId: (selector: string) => Element | null,
findByTestId: (selector: string) => Promise<Element | null>,
): Promise<void> {
// etc
Alternatively, if you have access to the functions or typings file, you can define the structure there, export the definition and use it in this file.
More reading about function types can be found at Typescript Documentation - More on Functions

Does typescript allow me to add a function declaration type instead of using a function expression?

Say I have the form
const mypluginCallback: FastifyPluginAsync<MyPluginOptions> = async (
fastify,
options,
) => {
};
And FastifyPluginAsync is a function type with the following form:
export type FastifyPluginAsync<Options extends FastifyPluginOptions = Record<never, never>, Server extends RawServerBase = RawServerDefault> = (
instance: FastifyInstance<Server, RawRequestDefaultExpression<Server>, RawReplyDefaultExpression<Server>>,
opts: Options
) => Promise<void>;
Is it possible to something similar but with a function declaration instead of a function expression?
function myPluginCallback() {}
If I wanted to reap the benefit of hoisting function declarations, how can I achieve this?
If I wanted to reap the benefit of hoisting function declarations, how can I achieve this?
I'm not entirely sure what you mean by this, but if what you're trying to do is set the return type of myPluginCallback using the function keyword to FastifyPluginAsync<MyPluginOptions> you can simply do the following:
function myPluginCallback(): FastifyPluginAsync<MyPluginOptions> {}
And if you need a promise, as you're using the async keyword in the arrow function, you can do this:
async function myPluginCallback(): Promise<FastifyPluginAsync<MyPluginOptions>> {}
Updated answer for updated question:
I believe this is what you're trying to do:
async function myPluginCallback<
Options extends FastifyPluginOptions = Record<never, never>,
Server extends RawServerBase = RawServerDefault
>(
instance: FastifyInstance<
Server,
RawRequestDefaultExpression<Server>,
RawReplyDefaultExpression<Server>
>,
options: Options,
): Promise<void> {
//
}
And this is how you would use it:
await myPluginCallback<MyPluginCallbackOptions, MyPluginCallbackServer>(instance, options) {
//
}

Typescript Expects Arguments Inspite of Defaults Provided to Function Arguments

I have code which looks like this :
export default async function logout(
_: null = null,
ctx: { session?: SessionContext } = {}
): Promise<void> {
return await ctx.session!.revoke();
}
Usage:
<a
onClick={async (): Promise<void> => {
await logout();
...
}}>
Logout
</a>
After searching through the questions here, I tried the suggested solution of adding expected outcome from the async function in the form Promise<void> & explicitly defining null type to the first arg. However, during the build process (Linter doesnt show any errors in VsCode editor), it fails with the error: Expected 1-2 arguments, but got 0.
What am I missing from the definitions to make it pass.

Flow Types with Promises (Fetch's)

I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
getCurrentJobAPI = (): {} => {
const url: string = `dummy_url&job_id=${this.props.currentJob}`;
return fetch(url, {credentials: 'include'})
.then((response) => {
return response.json();
})
.then((json: CurrentJob) => {
console.log(json);
const location = json.inventoryJob.location;
const ref_note = json.inventoryJob.note;
const id = json.inventoryJob.id;
const models = json.inventoryJobDetails.map((j) => {
return Object.assign({}, {
code: j.code,
qty: j.qty
})
});
this.setState({ currentCodes: models, location: location, ref_note: ref_note, id: id})
return json
})
.then((json: CurrentJob) => {
const barcodes = json.inventoryJob.history;
if (barcodes.length > 0) {
this.setState({apiBarcodes: barcodes})
}
this.calculateRows();
this.insertApiBarcodes();
this.setState({ initialLoad: true });
})
};
UPDATE:
Although I understand that I am supposed to define Promise<type> as the return value of getCurrentJobAPI (see Gilad's answer and comments), I am still unsure why I can't write Promise<CurrentJob> if the Fetch resolves as the JSON response.
[I have condensed my .then() statements per loganfsmyth's recommondation.]
Here are the type definitions for CurrentJob:
type Job = {
user_id: number,
status: 'open' | 'closed',
location: 'string',
history: {[number]: string}[],
note: string,
} & CommonCurrentJob;
type JobDetails = {
iaj_id: number,
code: number,
} & CommonCurrentJob;
type CommonCurrentJob = {
id: number,
qty: number,
qty_changed: number,
created_at: string,
updated_at: string
}
So first off, a disclaimer, I am a TypeScript user but I find that this question is actually applicable to both languages and has the same answer.
I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?
TL;DR: Promise<void> (see note). As you suspect, this is in fact the return type of the last top-level .then in the promise chain.
Now lets dig a bit deeper
Here is your example, reworked very slightly to leverage type inference instead of annotating callback parameters that are declared as any by their receivers.
As an aside, these callback parameter annotations amount to unsafe implicit casts, or type assertions as we call them in TypeScript, and they lie about the shape of the code. They look like this
declare function takesFn(fn: (args: any) => any): void;
So I have minimized these since they form a subtle trap
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI: () => Promise<void> = () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
return fetch(url, {credentials: 'include'})
.then(response => {
return (response : {json(): any}).json();
}) // --> Promise<any>
.then(json => {
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails
.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
return currentJob;
}) // --> Promise<CurrentJob>
.then(currentJob => {
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
}); // --> Promise<void>
};
}
So I am making assertions about the promises in each then call above but those assertions are all validated by type inference with the exception of the initial type cast on the response value.
As further evidence, if we remove the type declaration from the getCurrentJobAPI property of A, flow will infer that its type is in fact Promise<void>.
Bonus: simplifying with async/await. I've used several ESNext features above to shorten the code and make it a bit more pleasant, but we can leverage a specific feature, async/await to make it easier to understand control flow and types in Promise based code.
Consider this revision.
// #flow
import React from 'react';
type CurrentJob = {
inventoryJob: Job,
inventoryJobDetails: JobDetails[]
}
export default class A extends React.Component<{currentJob:JobDetails}, any> {
getCurrentJobAPI = async () => {
const url = `dummy_url&job_id=${String(this.props.currentJob)}`;
const response = await fetch(url, {credentials: 'include'});
const json = await response.json();
const currentJob = (json: CurrentJob); // make the assumption explicit.
console.log(currentJob);
const {location, id, note: ref_note} = currentJob.inventoryJob;
const currentCodes = currentJob.inventoryJobDetails.map(({code, qty}) => ({
code,
qty
}));
this.setState({currentCodes, location, ref_note, id});
const apiBarcodes = currentJob.inventoryJob.history;
if (apiBarcodes.length > 0) {
this.setState({apiBarcodes});
}
this.setState({initialLoad: true});
};
}
Clearly, this is a void function. It has no return statements. However, as an async function, it inherently returns a Promise, just as it did when written as an explicit Promise chain.
Note: void is a construct that has been found useful in Flow and TypeScript to represent the semantic intent of function that do not return values but in reality such functions actually return undefined because, well, this is JavaScript. Flow does not seem to recognize undefined as a type, but under TypeScript, the function could equally be annotated as returning Promise<undefined>. Irregardless, Promise<void> is preferable thanks to the clarity of intent it provides.
Remarks: I worked through this using a combination of https://flow.org/try and the flow binary for Windows. The experience on Windows is really terrible and hopefully it will improve.
When chaining then's, the result will always be a promise.
When calling then, the return value is another promise, otherwise chaining then's wouldn't have been possible.
You can see that easily by using console.log() surrounding the entire chain.

Categories

Resources