TypeScript Cheat Sheet for React and Next.js Development
Published: June 28, 2024
•Last Updated: June 28, 2024
Basic Types
let isDone: boolean = true; let count: number = 42; let name: string = "TypeScript"; let list: number[] = [1, 2, 3]; let tuple: [string, number] = ["hello", 10];
Interfaces
interface Person { name: string; age: number; } const user: Person = { name: "Alice", age: 25, };
Functions
function add(x: number, y: number): number { return x + y; } const addArrow = (x: number, y: number): number => x + y;
Classes
class Animal { name: string; constructor(name: string) { this.name = name; } move(distance: number = 0) { console.log(`${this.name} moved ${distance}m.`); } } const dog = new Animal("Dog"); dog.move(10);
Generics
function identity<T>(arg: T): T { return arg; } let output = identity<string>("myString");
Enums
enum Direction { Up = 1, Down, Left, Right, } let direction: Direction = Direction.Up;
Type Assertions
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
React Components with TypeScript
Function Component
import React from 'react'; interface Props { name: string; age?: number; // Optional prop } const Greeting: React.FC<Props> = ({ name, age }) => ( <div> <h1>Hello, {name}!</h1> {age && <p>Age: {age}</p>} </div> ); export default Greeting;
Class Component
import React, { Component } from 'react'; interface Props { initialCount: number; } interface State { count: number; } class Counter extends Component<Props, State> { state: State = { count: this.props.initialCount, }; increment = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.increment}>Increment</button> </div> ); } } export default Counter;
React Hooks with TypeScript
useState
import React, { useState } from 'react'; const Counter: React.FC = () => { const [count, setCount] = useState<number>(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default Counter;
useEffect
import React, { useEffect, useState } from 'react'; const DataFetcher: React.FC = () => { const [data, setData] = useState<string | null>(null); useEffect(() => { fetch('/api/data') .then(response => response.json()) .then(data => setData(data.message)); }, []); return <div>{data}</div>; }; export default DataFetcher;
Next.js with TypeScript
Pages
// pages/index.tsx import { NextPage } from 'next'; interface Props { message: string; } const HomePage: NextPage<Props> = ({ message }) => { return <h1>{message}</h1>; }; export async function getStaticProps() { return { props: { message: 'Hello from Next.js with TypeScript!', }, }; } export default HomePage;
API Routes
// pages/api/hello.ts import { NextApiRequest, NextApiResponse } from 'next'; export default (req: NextApiRequest, res: NextApiResponse) => { res.status(200).json({ message: 'Hello from Next.js API with TypeScript!' }); };
Additional Tips
- Props and State Types: Always define types for props and state to take full advantage of TypeScript’s type-checking.
- Default Props: Use default values in functional components to handle optional props.
- Strict Mode: Enable
strict
mode intsconfig.json
to get the most out of TypeScript’s type system.
{ "compilerOptions": { "strict": true, // other options... } }
Sure! Here are more examples to further illustrate TypeScript usage in React and Next.js.
Advanced Types
Union Types
let value: string | number; value = "Hello"; value = 42;
Intersection Types
interface Draggable { drag: () => void; } interface Resizable { resize: () => void; } type UIElement = Draggable & Resizable; const element: UIElement = { drag: () => console.log("Dragging"), resize: () => console.log("Resizing"), };
Type Guards
function isNumber(x: any): x is number { return typeof x === "number"; } function padLeft(value: string, padding: string | number) { if (isNumber(padding)) { return Array(padding + 1).join(" ") + value; } return padding + value; }
React Components with TypeScript
Context API
import React, { createContext, useContext, useState, ReactNode } from 'react'; interface User { name: string; age: number; } const UserContext = createContext<User | undefined>(undefined); const UserProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [user] = useState<User>({ name: "Alice", age: 25 }); return <UserContext.Provider value={user}>{children}</UserContext.Provider>; }; const UserProfile: React.FC = () => { const user = useContext(UserContext); if (!user) { return <div>Loading...</div>; } return ( <div> <h1>{user.name}</h1> <p>Age: {user.age}</p> </div> ); }; export default function App() { return ( <UserProvider> <UserProfile /> </UserProvider> ); }
Higher-Order Components (HOC)
import React from 'react'; interface InjectedProps { injectedProp: string; } const withInjectedProp = <P extends InjectedProps>( WrappedComponent: React.ComponentType<P> ) => { return (props: Omit<P, keyof InjectedProps>) => { return <WrappedComponent {...(props as P)} injectedProp="Injected Value" />; }; }; interface MyComponentProps { injectedProp: string; otherProp: number; } const MyComponent: React.FC<MyComponentProps> = ({ injectedProp, otherProp }) => ( <div> <p>{injectedProp}</p> <p>{otherProp}</p> </div> ); const EnhancedComponent = withInjectedProp(MyComponent); // Usage <EnhancedComponent otherProp={42} />;
Next.js with TypeScript
Custom App Component
// pages/_app.tsx import { AppProps } from 'next/app'; import '../styles/globals.css'; function MyApp({ Component, pageProps }: AppProps) { return <Component {...pageProps} />; } export default MyApp;
Custom Document
// pages/_document.tsx import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'; class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { const initialProps = await Document.getInitialProps(ctx); return { ...initialProps }; } render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ); } } export default MyDocument;
Dynamic Routes
// pages/[id].tsx import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; interface Props { id: string; } const Page: NextPage<Props> = ({ id }) => { return <h1>Page ID: {id}</h1>; }; export const getStaticPaths: GetStaticPaths = async () => { return { paths: [{ params: { id: '1' } }, { params: { id: '2' } }], fallback: false, }; }; export const getStaticProps: GetStaticProps = async (context) => { return { props: { id: context.params?.id, }, }; }; export default Page;
Custom Error Page
// pages/_error.tsx import { NextPageContext } from 'next'; interface ErrorProps { statusCode: number; } function Error({ statusCode }: ErrorProps) { return ( <p> {statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'} </p> ); } Error.getInitialProps = ({ res, err }: NextPageContext) => { const statusCode = res ? res.statusCode : err ? err.statusCode : 404; return { statusCode }; }; export default Error;
Sure! Here are more examples covering advanced patterns and integrations in TypeScript with React and Next.js.
Advanced React Patterns with TypeScript
Custom Hooks
import { useState, useEffect } from 'react'; interface User { id: number; name: string; } const useFetchUser = (userId: number) => { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState<boolean>(true); useEffect(() => { fetch(`/api/user/${userId}`) .then(response => response.json()) .then(data => { setUser(data); setLoading(false); }); }, [userId]); return { user, loading }; }; // Usage const UserComponent: React.FC<{ userId: number }> = ({ userId }) => { const { user, loading } = useFetchUser(userId); if (loading) return <div>Loading...</div>; if (!user) return <div>No user found</div>; return ( <div> <h1>{user.name}</h1> <p>ID: {user.id}</p> </div> ); };
Render Props
import React from 'react'; interface DataFetcherProps<T> { url: string; render: (data: T) => JSX.Element; } interface Data { message: string; } class DataFetcher<T> extends React.Component<DataFetcherProps<T>, { data: T | null }> { state = { data: null }; componentDidMount() { fetch(this.props.url) .then(response => response.json()) .then(data => this.setState({ data })); } render() { const { data } = this.state; if (!data) return <div>Loading...</div>; return this.props.render(data); } } // Usage const App: React.FC = () => ( <DataFetcher<Data> url="/api/data" render={(data) => <h1>{data.message}</h1>} /> );
Next.js with TypeScript
Custom Middleware
// middleware.ts import { NextRequest, NextResponse } from 'next/server'; export function middleware(req: NextRequest) { if (req.nextUrl.pathname.startsWith('/protected')) { const token = req.cookies.get('token'); if (!token) { return NextResponse.redirect('/login'); } } return NextResponse.next(); }
API Route with Dynamic Query
// pages/api/user/[id].ts import { NextApiRequest, NextApiResponse } from 'next'; export default (req: NextApiRequest, res: NextApiResponse) => { const { query: { id }, } = req; res.status(200).json({ id, name: `User ${id}` }); };
Redux with TypeScript in React
Setting Up Redux Store
// store/index.ts import { createStore } from 'redux'; import { Provider } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux'; interface State { count: number; } const initialState: State = { count: 0, }; type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' }; const reducer = (state = initialState, action: Action): State => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } }; const store = createStore(reducer); // Usage in React Component const Counter: React.FC = () => { const count = useSelector((state: State) => state.count); const dispatch = useDispatch(); return ( <div> <p>Count: {count}</p> <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button> </div> ); }; const App: React.FC = () => ( <Provider store={store}> <Counter /> </Provider> );
Form Handling with Formik and Yup
Formik and Yup Integration
import React from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; interface FormValues { email: string; password: string; } const SignupForm: React.FC = () => { const formik = useFormik<FormValues>({ initialValues: { email: '', password: '', }, validationSchema: Yup.object({ email: Yup.string().email('Invalid email address').required('Required'), password: Yup.string().min(6, 'Must be 6 characters or more').required('Required'), }), onSubmit: (values) => { console.log(values); }, }); return ( <form onSubmit={formik.handleSubmit}> <div> <label htmlFor="email">Email</label> <input id="email" name="email" type="email" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.email} /> {formik.touched.email && formik.errors.email ? ( <div>{formik.errors.email}</div> ) : null} </div> <div> <label htmlFor="password">Password</label> <input id="password" name="password" type="password" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.password} /> {formik.touched.password && formik.errors.password ? ( <div>{formik.errors.password}</div> ) : null} </div> <button type="submit">Submit</button> </form> ); }; export default SignupForm;
Handling Async Operations
Using Axios with TypeScript
import axios from 'axios'; interface User { id: number; name: string; } const fetchUser = async (id: number): Promise<User> => { const response = await axios.get<User>(`/api/user/${id}`); return response.data; }; // Usage in a component import React, { useState, useEffect } from 'react'; const UserComponent: React.FC<{ userId: number }> = ({ userId }) => { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState<boolean>(true); useEffect(() => { fetchUser(userId).then((data) => { setUser(data); setLoading(false); }); }, [userId]); if (loading) return <div>Loading...</div>; if (!user) return <div>No user found</div>; return ( <div> <h1>{user.name}</h1> <p>ID: {user.id}</p> </div> ); }; export default UserComponent;
These additional examples provide more advanced usage patterns and integrations for TypeScript in React and Next.js, covering custom hooks, context API, render props, Redux, form handling with Formik and Yup, and async operations with Axios.
Sure! Here are even more examples, including advanced TypeScript features and more React and Next.js integrations.
Advanced TypeScript Features
Mapped Types
type Keys = 'option1' | 'option2'; type Flags = { [K in Keys]: boolean }; const flags: Flags = { option1: true, option2: false, };
Conditional Types
type IsString<T> = T extends string ? "Yes" : "No"; type A = IsString<string>; // "Yes" type B = IsString<number>; // "No"
Utility Types
interface Person { name: string; age: number; address?: string; } type PartialPerson = Partial<Person>; type RequiredPerson = Required<Person>; type ReadonlyPerson = Readonly<Person>; type PersonWithoutAge = Omit<Person, 'age'>;
React with TypeScript
Error Boundaries
import React, { Component, ErrorInfo } from 'react'; interface State { hasError: boolean; } class ErrorBoundary extends Component<{}, State> { state = { hasError: false }; static getDerivedStateFromError(_: Error): State { return { hasError: true }; } componentDidCatch(error: Error, info: ErrorInfo) { console.error("Error:", error, "Info:", info); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } } export default ErrorBoundary; // Usage <ErrorBoundary> <YourComponent /> </ErrorBoundary>
Forwarding Refs
import React, { forwardRef, useRef } from 'react'; interface InputProps { label: string; } const Input = forwardRef<HTMLInputElement, InputProps>(({ label }, ref) => ( <div> <label>{label}</label> <input ref={ref} /> </div> )); // Usage const ParentComponent: React.FC = () => { const inputRef = useRef<HTMLInputElement>(null); const focusInput = () => { if (inputRef.current) { inputRef.current.focus(); } }; return ( <div> <Input label="Enter your name:" ref={inputRef} /> <button onClick={focusInput}>Focus Input</button> </div> ); };
Next.js with TypeScript
Using getServerSideProps
// pages/ssr.tsx import { GetServerSideProps, NextPage } from 'next'; interface Props { message: string; } const SSRPage: NextPage<Props> = ({ message }) => { return <h1>{message}</h1>; }; export const getServerSideProps: GetServerSideProps = async () => { return { props: { message: 'This is server-side rendered', }, }; }; export default SSRPage;
Using getStaticPaths with fallback
// pages/product/[id].tsx import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; interface Props { product: { id: string; name: string }; } const ProductPage: NextPage<Props> = ({ product }) => { return <h1>{product.name}</h1>; }; export const getStaticPaths: GetStaticPaths = async () => { const paths = [{ params: { id: '1' } }, { params: { id: '2' } }]; return { paths, fallback: true, }; }; export const getStaticProps: GetStaticProps = async (context) => { const { id } = context.params!; // Simulate fetching data from an API const product = { id, name: `Product ${id}` }; return { props: { product, }, }; }; export default ProductPage;
State Management with Recoil
Setting up Recoil
// atoms.ts import { atom } from 'recoil'; export const countState = atom<number>({ key: 'countState', default: 0, }); // Counter.tsx import React from 'react'; import { useRecoilState } from 'recoil'; import { countState } from './atoms'; const Counter: React.FC = () => { const [count, setCount] = useRecoilState(countState); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; // App.tsx import React from 'react'; import { RecoilRoot } from 'recoil'; import Counter from './Counter'; const App: React.FC = () => ( <RecoilRoot> <Counter /> </RecoilRoot> ); export default App;
Testing with React Testing Library and TypeScript
Writing Tests
// Counter.test.tsx import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import Counter from './Counter'; import { RecoilRoot } from 'recoil'; test('increments counter', () => { render( <RecoilRoot> <Counter /> </RecoilRoot> ); const button = screen.getByText('Increment'); fireEvent.click(button); expect(screen.getByText('Count: 1')).toBeInTheDocument(); });
These examples cover more advanced TypeScript features, React patterns like error boundaries and forwarding refs, Next.js functions for server-side rendering, Recoil for state management, and testing React components using React Testing Library. These should help you build more complex and robust applications using TypeScript with React and Next.js.