12 Prompts for JavaScript and TypeScript Code Generation: React, Node.js, and Utilities

Introduction

Generating JavaScript and TypeScript code with AI has become a standard skill for developers. The difference between a useful snippet and an unusable one often comes down to prompt design. This collection contains 12 copy-ready prompts for React components, Node.js APIs, and TypeScript utilities. Each prompt includes a problem statement, the exact prompt text, and a sample output. Where relevant, we reference official resources: the React documentation (react.dev), the TypeScript Handbook (typescriptlang.org), and MDN Web Docs.

1. Typed React Button Component

Problem: You need a reusable Button component with correct TypeScript types.

Prompt:

Act as a senior React and TypeScript developer. Create a functional `Button` component with props: `label: string`, `onClick: () => void`, `variant?: 'primary' | 'secondary'`, and `disabled?: boolean`. Provide a default value for `variant`. Use an interface for props and arrow function syntax.

Result:

import { FC } from 'react';

interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

export const Button: FC<ButtonProps> = ({
  label, onClick, variant = 'primary', disabled = false,
}) => (
  <button onClick={onClick} className={`btn ${variant}`} disabled={disabled}>
    {label}
  </button>
);

Why it works: It defines the role, tech stack, prop names, and types, so the output is immediately usable.

2. Custom React Hook with Fetch Cancel

Problem: Fetching data in React requires cleanup to avoid updates on unmounted components.

Prompt:

Make a React + TypeScript hook `useFetch<T>` that accepts a URL string and returns `{ data, loading, error }`. Use `AbortController` to cancel the fetch when the component unmounts. Include a ref to avoid state updates after unmount. Add proper TypeScript generics.

Result:

import { useEffect, useState } from 'react';

export function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<unknown>(null);

  useEffect(() => {
    const controller = new AbortController();
    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(data => setData(data as T))
      .catch(err => { if (controller.signal.aborted) return; setError(err); })
      .finally(() => setLoading(false));
    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

3. React Context with TypeScript

Problem: Creating global state with Context and TypeScript is verbose.

Prompt:

Generate a React Context for a theme with 'light' and 'dark' modes. Use TypeScript. The context should provide `theme` and `setTheme`. Create a custom hook `useTheme` and a `ThemeProvider` component. Include the required type definitions.

Result:

import { createContext, useContext, useState, FC, ReactNode } from 'react';

type Theme = 'light' | 'dark';
interface ThemeContextValue { theme: Theme; setTheme: (t: Theme) => void; }
const ThemeContext = createContext<ThemeContextValue | null>(null);

export const ThemeProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const [theme, setTheme] = useState<Theme>('light');
  return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
};

export const useTheme = () => {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
  return ctx;
};

4. Node.js REST Endpoint with Validation

Problem: You have an Express server and need a POST endpoint with validation.

Prompt:

Act as a Node.js developer. Build an Express endpoint `POST /api/users` that accepts a JSON body with `email` and `password`. Use Joi to validate the input. Return 400 with error details if invalid, and 201 with the created user otherwise. Assume a mock database object. Write in pure JavaScript with comments.

Result:

const express = require('express');
const Joi = require('joi');
const router = express.Router();

const schema = Joi.object({
  email: Joi.string().email().required(),
  password: Joi.string().min(8).required()
});

router.post('/users', (req, res) => {
  const { error, value } = schema.validate(req.body);
  if (error) return res.status(400).json({ error: error.details });
  // In a real app, save `value` to a database
  res.status(201).json({ id: 1, email: value.email });
});

5. Node.js Async File Reader

Problem: You need to read multiple files concurrently and return their contents.

Prompt:

Write a Node.js script that reads three text files (`a.txt`, `b.txt`, `c.txt`) asynchronously using promises. Use `fs/promises`. Handle errors gracefully and print the combined contents. Use `async/await` and try/catch.

Result:

const fs = require('fs/promises');

async function readFiles() {
  try {
    const [a, b, c] = await Promise.all([
      fs.readFile('a.txt', 'utf8'),
      fs.readFile('b.txt', 'utf8'),
      fs.readFile('c.txt', 'utf8')
    ]);
    console.log(a + b + c);
  } catch (err) {
    console.error('Failed to read files:', err);
  }
}
readFiles();

6. TypeScript Deep Partial Utility

Problem: You want a recursive DeepPartial<T> that makes all nested properties optional.

Prompt:

Create a TypeScript utility type `DeepPartial<T>` that maps over all properties and recursively makes nested objects optional. Provide an example usage.

Result:

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface Config { server: { port: number; host: string }; debug: boolean; }
const partialConfig: DeepPartial<Config> = { server: { port: 3000 } };

7. TypeScript Generic with Constraint

Problem: You need a function that works with any object that has an id property.

Prompt:

Write a TypeScript function `getById<T extends { id: string }>(items: T[], id: string): T | undefined`. Include an explicit return type and a usage example.

Result:

interface Entity { id: string; name: string; }

function getById<T extends { id: string }>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

const users: Entity[] = [
  { id: 'a1', name: 'Alice' },
  { id: 'b2', name: 'Bob' }
];
const user = getById(users, 'a1'); // { id: 'a1', name: 'Alice' }

8. Debounce Function in TypeScript

Problem: You need a debounce utility to limit how often a function is called, with proper TypeScript typing.

Prompt:

Implement a debounce function in TypeScript. It should take a function `fn` and delay in milliseconds, and return a debounced version. Preserve the function type using generics and `ReturnType`.

Result:

function debounce<F extends (...args: any[]) => void>(
  fn: F,
  delay: number
): (...args: Parameters<F>) => void {
  let timer: ReturnType<typeof setTimeout> | undefined;
  return (...args: Parameters<F>) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

const log = debounce(() => console.log('hi'), 300);

9. Promisify Callback Function

Problem: You have a callback-based API and want to convert it to Promises.

Prompt:

Write a TypeScript utility `promisify` that converts a node-style callback function (err, result) into a promise-based function. Use generics to preserve the result type. Show an example with `fs.readFile`.

Result:

function promisify<T>(fn: (callback: (err: Error | null, result?: T) => void) => void): () => Promise<T> {
  return () => new Promise((resolve, reject) => {
    fn((err, result) => {
      if (err) reject(err); else resolve(result);
    });
  });
}

const readFilePromise = promisify(fs.readFile);
// Usage: readFilePromise().then(console.log)

10. Generate TypeScript Types from JSON

Problem: You have a JSON structure and you want to generate TypeScript interfaces automatically.

Prompt:

Convert the following JSON to TypeScript interfaces. Include nested objects and arrays. Use `interface` or `type`, and name the top-level interface `ApiResponse`.
JSON: { "status": "ok", "data": { "user": { "id": 42, "name": "Alice" }, "tags": ["admin", "editor"] } }

Result:

interface ApiResponse {
  status: string;
  data: {
    user: { id: number; name: string; };
    tags: string[];
  };
}

11. React Component Unit Test

Problem: You need a test for a Button component using Jest and React Testing Library.

Prompt:

Write a Jest test suite for the React component `Button` which takes `label` and `onClick`. Use `@testing-library/react`. Include tests for rendering the label, calling onClick on click, and disabling the button.

Result:

import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

test('renders label', () => {
  render(<Button label='Submit' onClick={() => {}} />);
  expect(screen.getByText('Submit')).toBeInTheDocument();
});

test('onClick fires', () => {
  const fn = jest.fn();
  render(<Button label='Go' onClick={fn} />);
  fireEvent.click(screen.getByText('Go'));
  expect(fn).toHaveBeenCalledTimes(1);
});

test('disabled button blocks click', () => {
  const fn = jest.fn();
  render(<Button label='No' onClick={fn} disabled />);
  fireEvent.click(screen.getByText('No'));
  expect(fn).not.toHaveBeenCalled();
});

12. useReducer with TypeScript

Problem: You need to manage a counter using useReducer with typed actions.

Prompt:

Create a counter component in React using `useReducer`. Define the state type and action union. Actions: `{ type: 'increment' }`, `{ type: 'decrement' }`, and `{ type: 'set', payload: number }`. Provide the reducer and component.

Result:

import { useReducer } from 'react';

type State = { count: number };
type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'set'; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    case 'set': return { count: action.payload };
    default: return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <div>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

Conclusion

These prompts work because they include role, context, exact requirements, and constraints. When generating code, always review the output against the official documentation—React, Node.js, and TypeScript docs evolve. For complex tasks, break the request into smaller steps and iterate. The prompts above are a starting point, not a final answer. Copy them, tweak them, and integrate them into your daily coding workflow.

← All posts

Comments