properly typing props in a Next.js page [duplicate] - reactjs

This question already has an answer here:
How to type a page component with props in Next.js?
(1 answer)
Closed 5 months ago.
I have a fairly straight forward SSR-generated Next.js page. My typing is not correct somewhere along the path here, and the linter is complaining.
export interface ProposalTag {
id: number;
name: string;
hex: string;
color: string;
}
export async function getProposalTags(): Promise<ProposalTag[]> {
// query for response
const tags = response?.data?.proposal_tags.map((tag: ProposalTag) => {
return {
id: tag.id,
name: tag.name,
hex: tag.hex,
color: tag.color,
};
});
return tags;
}
export const getServerSideProps: GetServerSideProps = async context => {
const proposalTags: ProposalTag[] = await getProposalTags();
return {
props: {
proposalTags,
},
};
};
const Proposal: NextPage = ({ proposalTags }) => { /* code */ }
In this particular case, the linter is complaining with the following:
Property 'proposalTags' does not exist on type '{}'
I'm not entirely sure what I can do to please the typescript interpreter/linter here

NextPage is a generic type with optional generic parameters, and the first one is the prop types for the component:
const Proposal: NextPage<{
proposalTags: ProposalTag[];
}> = ({ proposalTags }) => { /* code */ }

Related

React typescript conditional types

I have a hook that returns data. Its type is depending on the props passed.
interface MyHookProps<T, TF> {
transform?: (data: T) => TF
}
const myHook = <T extends any = any, TF = any>(props?: MyHookProps<T, TF>) => {
const [items, setItems] = useState([])
return { data: items }
}
I want my data to be the return type of transform when it is passed as prop and T when its not
example:
interface User {
name: string;
age: number;
}
const { data } = myHook<User[]>();
Its type should be User[], but when you do it like this:
const { data } = myHook<User[]>({
transform: (data) => data.map(d => ({ ...d, another: 1 }))
})
Its type should be
{
name: string;
age: number;
another: string;
}[]
This is currently not possible as written in the question as TypeScript lacks partial type inference. Providing an explicit type parameter will prevent inference of others.
We can use currying to work around this problem.
const myHook = <T extends any>() => <TF extends any>(props?: MyHookProps<T, TF>) => {
const [items, setItems] = useState([])
return { data: items } as { data: unknown extends TF ? T : TF }
}
Here we "chain" two functions together. The first one uses T as the generic type while the second one can infer TF. In the conditional type at the return statement, we check if TF could be inferred. If not, we return T.
interface User {
name: string;
age: number;
}
namespace N1 {
const { data } = myHook<User[]>()();
}
namespace N2 {
const { data } = myHook<User[]>()({
transform: (data) => data.map(d => ({ ...d, another: 1 }))
})
}
Playground

Argument of type 'never[]' is not assignable to parameter of type 'Comment | (() => Comment)'

I've been for some hours tryin to solve this, I've made the code below using as base another component pretty equal from the same project. What am I doing wrong?
The error is exactly what I've put in title: Argument of type 'never[]' is not assignable to parameter of type 'Comment | (() => Comment)'. I think it's something about the interface or the state above, idk.
import React, { useState, useEffect } from 'react';
import { collection, getDocs } from 'firebase/firestore';
import { db } from '../../services/firebase';
import { CommentListDiv } from './styles';
interface Comment {
email: string;
message: string;
name: string;
}
const CommentList = ({ pokemon }: { pokemon: any }) => {
const [comments, setAllComments] = useState<Comment>([]);
const collectionRef = collection(db, `comments-${pokemon.id}`);
const loadAllComments = async (): Promise<any> => {
await getDocs(collectionRef).then((snapshot) => {
const allComments: any = snapshot.docs.map((doc) => doc.data());
console.log(allComments);
console.log(comments);
setAllComments(allComments);
});
};
useEffect(() => {
loadAllComments();
});
return (
<div>
<CommentListDiv>
<h1>Comments about {pokemon.name}</h1>
<h2>Name: {comments.name}</h2>
<h2>E-Mail: {comments.email}</h2>
<p>
Message:
<br />
{comments.message}
</p>
</CommentListDiv>
</div>
);
};
export default CommentList;
This is the return of both console.logs (the return is correct, the exact entry that I've made and that's showing at Firebase too:
useState<Comment>([]);
This says that the state will contain exactly one Comment, but then you try to pass an array in as the initial value. If the state is supposed to store an array of comments, then do:
useState<Comment[]>([]);

Dynamically Set Type of Prop Based on Another Prop

If I have an interface like the following:
interface Example {
Component: React.ReactElement;
componentProperties: typeof Example.Component;
}
Is there a way to get the type of the properties that that component passed in expects. So say I pass in a custom component like the following:
const Text = ({ value: string }) => <p>{value}</p>;
The type of componentProperties I would like to equal { value: string }
I think I am looking something closer to this where the typeof the Component is what determines the type for componentProperties:
interface Example<T> {
Component: () => React.ReactElement;
componentProperties: T;
}
const Comp = ({ value }: { value: string}) => <p>{value}</p>;
const blah = ({ Component, componentProperties }: Example<typeof Component>) => {
return <Component {...componentProperties} />;
};
blah({
Component: Comp,
componentProperties: { value: 'test' }
});
This example is riddled with errors which is why I am having trouble.
Everything looks good except the blah, I would guess
there you get the errors.
This is how I solved this on my project.
const blah = ({ Component, componentProperties }: Example<typeof Component>) => {
var Cm = Component as any;
return <Cm {...componentProperties} />;
};
this is a common use case for generics, eg:
interface Example<T> {
Component: React.ReactElement;
componentProperties: T;
}

how can i give type in getServerSideProps of Nextjs with typescript?

I'm using NextJs + TypeScript to make a little clone project, but I got a problem with type in getServerSideProps.
As you can see, in getServerSideProps, I am fetching data using with context.query.
But some error message is not fixed and I don't understand why that error appears.
The error message is this.
Type 'string[]' cannot be used as an index type.ts(2538)
Type 'undefined' cannot be used as an index type.ts(2538)
const genre: string | string[] | undefined
How can I fix this type problem?
import Head from "next/head";
import Nav from "../components/Nav/Nav";
import Header from "../components/Header/Header";
import Results from "../components/Results/Results";
import requests from "../utils/requests";
import { GetServerSideProps } from "next";
type HomeProps = {
results: {}[];
};
export default function Home({ results }: HomeProps) {
console.log(results);
return (
<div>
<Results results={results} />
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const genre = context.query.genre
const response = await fetch(
`https://api.themoviedb.org/3${
requests[genre]?.url {/*this is problem line*/}
|| requests.fetchTopRated.url
}`
);
const data = await response.json();
return {
props: { results: data.results },
};
};
You can use like this;
type PageProps = {
isAuthanticated: boolean,
categories?: CategoryType[]
}
export const getServerSideProps: GetServerSideProps<PageProps> = async (context) => {
const _props: PageProps = {
isAuthanticated: auth,
categories: data.results
}
return { props: _props }
};
const Category: NextPage<PageProps> = (props) => {
return(
...
)
};
Since the type of genre can be string or string[] (or undefined), it can not be used to index requests without being narrowed down to string via the use of an if statement:
if (typeof genre === 'string') {
// Typescript now knows it is a string
const response = await fetch(
`https://api.themoviedb.org/3${
requests[genre]?.fetchTopRated.url {/*this is problem line*/}
|| requests.fetchTopRated.url
}`
);
const data = await response.json();
return {
props: { results: data.results },
};
} else if (typeof genre == 'object'){
// handle case where it is an array
} else {
// genre is undefined
}
When you receive params via context, the value could be either string or string[] (or undefined) so you need to cast. It could be a single genre or multiple genres in the URL.
?genre=film or ?genre=film&genre=music
For you case, simply cast as string:
const genre = context.query.genre as string;
UPDATE
As per your comments, the first issue that you raised in the question was actually about casting to string as above.
The second issue, which you should not actually be seeing and must be a TS or module config issue, is related to trying to accessing a key as string by index on your vanilla object exported from "../utils/requests";
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ fetchTrending: { title: string; url: string; }; ...
Your data object has literal key names:
// ../utils/requests
export default {
fetchTrending: {
title: "Trending",
url: /trending/all/week?api_key=${API_KEY}&language=en-US,
},
fetchTopRated: {
title: "Top Rated",
url: /movie/top_rated?api_key=${API_KEY}&language=en-US,
},
};
Rather define the type like this:
export interface IRequest {
[name: string]: {
title: string;
url: string;
};
}
const data: IRequest = {
fetchTrending: {
title: "Trending",
url: `/trending/all/week?api_key=${API_KEY}&language=en-US1`
},
fetchTopRated: {
title: "Top Rated",
url: `/movie/top_rated?api_key=${API_KEY}&language=en-US`
}
};
export default data;
or you could use a Record to have strongly typed keys:
type RequestNames = "fetchTrending" | "fetchTopRated";
export const records: Record<
RequestNames,
{
title: string;
url: string;
}
> = {
fetchTrending: {
title: "Trending",
url: `/trending/all/week?api_key=${API_KEY}&language=en-US1`
},
fetchTopRated: {
title: "Top Rated",
url: `/movie/top_rated?api_key=${API_KEY}&language=en-US`
}
};

I cheated to make redux work, how does this work?

So I'm learning react but I am no javascript fan so I'm using typescript. Because of that I try to type things but it seems like using any is always the cheat way out. Here's my container:
interface UserProfileRouteProps {
userId: string
}
export function mapStateToProps(state: StoreState, props: RouteComponentProps<UserProfileRouteProps>) {
let userId: number = parseInt(props.match.params.userId)
const user = state.users.find(u => u.id === userId)
return {
id: userId,
name: user.name,
foo: user.foo
}
}
export function mapDispatchToProps(dispatch: Dispatch<actions.UserProfileActions>, props: RouteComponentProps<UserProfileRouteProps>) {
let userId: number = parseInt(props.match.params.userId)
return {
onFoo: () => dispatch(actions.foo()),
onDelete: () => dispatch(actions.delete(recipeId))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile as any)
Here's the component
export interface UserProfileProps{
id: number
name: string
foo: IFoo
onFoo?: () => void
onDelete?: () => void
}
export class UserProfile extends React.Component<UserProfileProps, void>{
Now I wanted my mapStateToProps and mapDispatchToProps to be strongly-typed and have a return type of UserProfileProps but I couldn't do that because connect() threw an error:
error TS2345: Argument of type 'typeof UserProfile' is not assignable to parameter of type 'ComponentType<UserProfileProps & { onFoo: () => { type: string; }; onDelete: () => ...'
so my cheat after googling was to go UserProfile as any.
What's actually happening here? Am I doing this right? Should I have separate container props and component props? I was trying to make the component unaware of the routing.
you can use the generic form of connect
export default connect<{}, {}, UserProfileProps>(
mapStateToProps,
mapDispatchToProps
)(UserProfile);

Resources