Comprehensive React Cheat Sheet

useState with TypeScript

import React, { useState } from 'react';

interface User {
  name: string;
  age: number;
}

function UserProfile() {
  const [user, setUser] = useState<User | null>(null);

  const updateUser = () => {
    setUser({ name: 'John Doe', age: 30 });
  };

  return (
    <div>
      {user ? (
        <>
          <p>Name: {user.name}</p>
          <p>Age: {user.age}</p>
        </>
      ) : (
        <button onClick={updateUser}>Set User</button>
      )}
    </div>
  );
}

useEffect Cleanup

import React, { useState, useEffect } from 'react';

function SubscriptionComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    let isMounted = true;
    const subscription = subscribeToData(newData => {
      if (isMounted) {
        setData(newData);
      }
    });

    return () => {
      isMounted = false;
      subscription.unsubscribe();
    };
  }, []);

  return <div>{data ? `Data: ${data}` : 'Loading...'}</div>;
}

useContext with TypeScript

import React, { createContext, useContext, useState } from 'react';

interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export const ThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

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

// Usage
const ThemedButton: React.FC = () => {
  const { theme, toggleTheme } = useTheme();
  return (
    <button onClick={toggleTheme} style={{ background: theme === 'light' ? '#fff' : '#333' }}>
      Toggle Theme
    </button>
  );
};

React.memo with Custom Comparison

import React, { memo } from 'react';

const ExpensiveComponent = memo(({ data }) => {
  // Render using data
}, (prevProps, nextProps) => {
  // Custom comparison function
  return prevProps.data.id === nextProps.data.id;
});

// Usage
function ParentComponent() {
  const data = { id: 1, value: Math.random() };
  return <ExpensiveComponent data={data} />;
}

useMemo for Expensive Calculations

import React, { useState, useMemo } from 'react';

function Fibonacci({ n }) {
  const result = useMemo(() => {
    const fib = (num) => {
      if (num <= 1) return num;
      return fib(num - 1) + fib(num - 2);
    };
    return fib(n);
  }, [n]);

  return <div>Fibonacci of {n} is {result}</div>;
}

Render Props Pattern

import React, { useState } from 'react';

const Toggle = ({ children }) => {
  const [on, setOn] = useState(false);
  const toggle = () => setOn(!on);

  return children({ on, toggle });
};

// Usage
function App() {
  return (
    <Toggle>
      {({ on, toggle }) => (
        <div>
          {on ? 'ON' : 'OFF'}
          <button onClick={toggle}>Toggle</button>
        </div>
      )}
    </Toggle>
  );
}

Compound Components Pattern

import React, { createContext, useContext, useState } from 'react';

const ToggleContext = createContext();

function Toggle({ children }) {
  const [on, setOn] = useState(false);
  const toggle = () => setOn(!on);

  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  );
}

Toggle.On = ({ children }) => {
  const { on } = useContext(ToggleContext);
  return on ? children : null;
};

Toggle.Off = ({ children }) => {
  const { on } = useContext(ToggleContext);
  return on ? null : children;
};

Toggle.Button = () => {
  const { on, toggle } = useContext(ToggleContext);
  return <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>;
};

// Usage
function App() {
  return (
    <Toggle>
      <Toggle.On>The button is on</Toggle.On>
      <Toggle.Off>The button is off</Toggle.Off>
      <Toggle.Button />
    </Toggle>
  );
}

useReducer with TypeScript

import React, { useReducer } from 'react';

type State = {
  count: number;
};

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' };

const initialState: State = { count: 0 };

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 'reset':
      return initialState;
    default:
      throw new Error();
  }
}

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

forwardRef and useImperativeHandle

import React, { forwardRef, useImperativeHandle, useRef } from 'react';

const FancyInput = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));

  return <input ref={inputRef} {...props} />;
});

// Usage
function Form() {
  const fancyInputRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(fancyInputRef.current.getValue());
  };

  return (
    <form onSubmit={handleSubmit}>
      <FancyInput ref={fancyInputRef} />
      <button type="submit">Submit</button>
    </form>
  );
}

Testing Hooks with React Testing Library

import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <>
      <span data-testid="count">{count}</span>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}

test('Counter increments when button is clicked', () => {
  const { getByTestId, getByText } = render(<Counter />);
  const countElement = getByTestId('count');
  const incrementButton = getByText('Increment');

  expect(countElement).toHaveTextContent('0');
  fireEvent.click(incrementButton);
  expect(countElement).toHaveTextContent('1');
});

Mocking Context in Tests

import React, { useContext } from 'react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme === 'light' ? '#fff' : '#000' }}>Theme: {theme}</button>;
}

test('ThemedButton uses the correct theme', () => {
  const { getByText } = render(
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );

  const button = getByText('Theme: dark');
  expect(button).toHaveStyle('background: #000');
});

Styled-components with TypeScript

import styled from 'styled-components';

interface ButtonProps {
  primary?: boolean;
  backgroundColor?: string;
}

const Button = styled.button<ButtonProps>`
  background-color: ${(props) => 
    props.backgroundColor || (props.primary ? '#007bff' : '#6c757d')};
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    opacity: 0.8;
  }
`;

// Usage
function App() {
  return (
    <>
      <Button primary>Primary Button</Button>
      <Button>Secondary Button</Button>
      <Button backgroundColor="#28a745">Custom Button</Button>
    </>
  );
}

Custom Hook: useLocalStorage

import { useState, useEffect } from 'react';

function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(storedValue));
  }, [key, storedValue]);

  return [storedValue, setStoredValue];
}

// Usage
function App() {
  const [name, setName] = useLocalStorage<string>('name', 'John');

  return (
    <input
      type="text"
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
  );
}

React.lazy and Suspense

import React, { Suspense, lazy } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

Higher-Order Component (HOC) Pattern

import React from 'react';

function withLogging(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
      console.log(`Component ${WrappedComponent.name} mounted`);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

// Usage
const MyComponent = ({ name }) => <div>Hello, {name}!</div>;
const EnhancedComponent = withLogging(MyComponent);

function App() {
  return <EnhancedComponent name="John" />;
}