React Rules for Cursor Editor

1 Rule
UI Framework
.mdc Format

About These Rules

These React coding rules are specifically formatted for the Cursor Editor as .mdc files, focusing on React 18+ features and modern component patterns. Each rule is designed to help you write more maintainable React applications while leveraging Cursor's AI capabilities for enhanced development.

1. Custom Hooks and Component Composition

Implement reusable custom hooks and component composition patterns for better code organization and maintainability. This approach demonstrates modern React patterns including hook composition, render props, and TypeScript integration.

react-hooks-composition.mdc
import { useState, useEffect, useCallback, ReactNode } from 'react';

// Generic async state hook with TypeScript
interface AsyncState {
    data: T | null;
    loading: boolean;
    error: Error | null;
}

function useAsync(asyncFn: () => Promise, deps: any[] = []): AsyncState {
    const [state, setState] = useState>({
        data: null,
        loading: true,
        error: null,
    });

    useEffect(() => {
        let mounted = true;

        const fetchData = async () => {
            setState(prev => ({ ...prev, loading: true }));
            try {
                const result = await asyncFn();
                if (mounted) {
                    setState({ data: result, loading: false, error: null });
                }
            } catch (error) {
                if (mounted) {
                    setState({ data: null, loading: false, error });
                }
            }
        };

        fetchData();
        return () => { mounted = false; };
    }, deps);

    return state;
}

// Composable form hook with validation
interface FormField {
    value: T;
    error?: string;
    touched: boolean;
}

type FormState = {
    [K in keyof T]: FormField;
};

interface UseFormOptions {
    initialValues: T;
    validate?: (values: T) => Partial>;
    onSubmit: (values: T) => Promise;
}

function useForm>({
    initialValues,
    validate,
    onSubmit,
}: UseFormOptions) {
    const [formState, setFormState] = useState>(() => 
        Object.keys(initialValues).reduce((acc, key) => ({
            ...acc,
            [key]: {
                value: initialValues[key],
                touched: false,
            },
        }), {} as FormState)
    );

    const handleChange = useCallback((name: keyof T, value: T[keyof T]) => {
        setFormState(prev => ({
            ...prev,
            [name]: {
                ...prev[name],
                value,
                touched: true,
            },
        }));
    }, []);

    const handleSubmit = useCallback(async (e: React.FormEvent) => {
        e.preventDefault();
        
        if (validate) {
            const values = Object.keys(formState).reduce((acc, key) => ({
                ...acc,
                [key]: formState[key].value,
            }), {} as T);
            
            const errors = validate(values);
            if (Object.keys(errors).length > 0) {
                setFormState(prev => 
                    Object.keys(prev).reduce((acc, key) => ({
                        ...acc,
                        [key]: {
                            ...prev[key],
                            error: errors[key],
                            touched: true,
                        },
                    }), {} as FormState)
                );
                return;
            }
        }

        await onSubmit(
            Object.keys(formState).reduce((acc, key) => ({
                ...acc,
                [key]: formState[key].value,
            }), {} as T)
        );
    }, [formState, validate, onSubmit]);

    return { formState, handleChange, handleSubmit };
}

// Example usage with TypeScript and composition
interface User {
    name: string;
    email: string;
    age: number;
}

interface DataProviderProps {
    children: (state: AsyncState) => ReactNode;
    fetchData: () => Promise;
}

function DataProvider({ children, fetchData }: DataProviderProps) {
    const state = useAsync(fetchData);
    return <>{children(state)};
}

function UserForm() {
    const { formState, handleChange, handleSubmit } = useForm({
        initialValues: {
            name: '',
            email: '',
            age: 0,
        },
        validate: (values) => {
            const errors: Partial> = {};
            if (!values.name) errors.name = 'Name is required';
            if (!values.email) errors.email = 'Email is required';
            if (values.age < 18) errors.age = 'Must be 18 or older';
            return errors;
        },
        onSubmit: async (values) => {
            // Submit logic here
            console.log(values);
        },
    });

    return (
        
handleChange('name', e.target.value)} /> {formState.name.error && formState.name.touched && ( {formState.name.error} )}
{/* Similar fields for email and age */}
); }

Why This Rule Matters

  • Promotes code reusability through custom hooks
  • Ensures type safety with TypeScript integration
  • Improves component organization and maintainability
  • Reduces code duplication
  • Makes testing easier with isolated logic

Using with Cursor

Cursor's AI capabilities can help you:

  • Generate custom hooks based on usage patterns
  • Suggest component composition strategies
  • Identify opportunities for hook extraction
  • Debug React hooks dependencies
  • Convert class components to functional components with hooks