DEV Community

Cover image for 20 React Tricks Every Developer Should Know
Shayan
Shayan

Posted on

7 1 1 1 1

20 React Tricks Every Developer Should Know

I've been building UserJot (a user feedback and roadmap platform) for the past several months, and it's been my deepest dive into React yet. After many hours of writing production React code, dealing with performance issues, refactoring components, and discovering better patterns, I've accumulated a collection of tricks that have genuinely improved my development workflow.

UserJot Dashboard

These aren't just theoretical concepts. They're practical patterns I use daily. Some saved me from bugs, others made my code cleaner, and a few completely changed how I approach React development. Here are 20 tricks that I think every React developer should have in their toolkit.

1. Use the key prop to reset component state

When you need to completely reset a component's internal state, just change its key. This forces React to unmount and remount the component with fresh state.

function App() {
  const [userId, setUserId] = useState(1);

  return (
    <UserProfile 
      key={userId} // Changing userId will reset UserProfile's state
      userId={userId} 
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

2. Conditional rendering with short-circuit evaluation

Instead of ternary operators that return null, use && for cleaner conditional rendering. Just watch out for falsy values like 0 that might render unexpectedly.

// Good
{isLoggedIn && <UserDashboard />}

// Careful with numbers
{count > 0 && <Badge count={count} />} // Use comparison to avoid rendering "0"
Enter fullscreen mode Exit fullscreen mode

3. Use optional chaining in event handlers

Prevent crashes when dealing with potentially undefined refs or DOM elements by using optional chaining.

const handleClick = () => {
  inputRef.current?.focus();
  formRef.current?.scrollIntoView({ behavior: 'smooth' });
};
Enter fullscreen mode Exit fullscreen mode

4. Destructure props with default values

Set default values right in the destructuring pattern to keep your components defensive and reduce the need for defaultProps.

function Button({ 
  text = 'Click me', 
  onClick = () => {}, 
  disabled = false 
}) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {text}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Use useId for accessible forms

React 18's useId hook generates stable unique IDs perfect for linking form labels to inputs without worrying about SSR mismatches.

function FormField({ label, type = 'text' }) {
  const id = useId();

  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} type={type} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

6. Memoize expensive computations with useMemo

Don't recreate complex objects or run expensive calculations on every render. Use useMemo to cache results between renders.

const expensiveData = useMemo(() => {
  return processLargeDataset(rawData);
}, [rawData]); // Only recompute when rawData changes
Enter fullscreen mode Exit fullscreen mode

7. Use useCallback for stable function references

Prevent unnecessary re-renders in child components by keeping function references stable with useCallback.

const handleSearch = useCallback((query) => {
  // Search logic here
  setResults(searchItems(query));
}, [searchItems]); // Dependencies ensure the function updates when needed

// Now SearchBar won't re-render unless handleSearch actually changes
<SearchBar onSearch={handleSearch} />
Enter fullscreen mode Exit fullscreen mode

8. Create custom hooks for reusable logic

Extract component logic into custom hooks to share stateful logic between components without prop drilling or context overhead.

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initialValue;
  });

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

  return [value, setValue];
}

// Usage
const [theme, setTheme] = useLocalStorage('theme', 'dark');
Enter fullscreen mode Exit fullscreen mode

9. Use React.lazy for code splitting

Split your bundle by lazy loading components that aren't immediately needed. Combine with Suspense for loading states.

const Analytics = React.lazy(() => import('./Analytics'));

function Dashboard() {
  return (
    <Suspense fallback={<Spinner />}>
      <Analytics />
    </Suspense>
  );
}
Enter fullscreen mode Exit fullscreen mode

10. Optimize lists with React.memo

For large lists, wrap list items in React.memo to prevent unnecessary re-renders when the parent updates.

const ListItem = React.memo(({ item, onSelect }) => {
  return (
    <li onClick={() => onSelect(item.id)}>
      {item.name}
    </li>
  );
}, (prevProps, nextProps) => {
  // Custom comparison function (optional)
  return prevProps.item.id === nextProps.item.id;
});
Enter fullscreen mode Exit fullscreen mode

11. Use refs for DOM measurements

Need element dimensions or positions? Use refs with useLayoutEffect to measure DOM elements after they render.

function MeasuredComponent() {
  const ref = useRef();
  const [dimensions, setDimensions] = useState({});

  useLayoutEffect(() => {
    if (ref.current) {
      const { width, height } = ref.current.getBoundingClientRect();
      setDimensions({ width, height });
    }
  }, []);

  return <div ref={ref}>Content</div>;
}
Enter fullscreen mode Exit fullscreen mode

12. Compose components with children prop patterns

Create flexible wrapper components using children patterns for better composition.

function Card({ children, footer }) {
  return (
    <div className="card">
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// Usage
<Card footer={<Button>Save</Button>}>
  <h2>Title</h2>
  <p>Content goes here</p>
</Card>
Enter fullscreen mode Exit fullscreen mode

13. Handle async effects with cleanup

Always clean up async operations in useEffect to prevent memory leaks and state updates on unmounted components.

useEffect(() => {
  let cancelled = false;

  async function fetchData() {
    const data = await api.getData();
    if (!cancelled) {
      setData(data);
    }
  }

  fetchData();

  return () => {
    cancelled = true;
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

14. Use portal for modals and tooltips

Render components outside their parent DOM hierarchy while keeping them in the React tree with createPortal.

function Modal({ children, isOpen }) {
  if (!isOpen) return null;

  return ReactDOM.createPortal(
    <div className="modal-backdrop">
      <div className="modal">{children}</div>
    </div>,
    document.getElementById('modal-root')
  );
}
Enter fullscreen mode Exit fullscreen mode

15. Debounce expensive operations

Use debouncing for search inputs or other expensive operations triggered by user input.

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Usage
const debouncedSearch = useDebounce(searchTerm, 300);
Enter fullscreen mode Exit fullscreen mode

16. Use reducer for complex state logic

When useState gets unwieldy with multiple related state updates, switch to useReducer for better organization.

const initialState = { count: 0, step: 1 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <input 
        value={state.step}
        onChange={(e) => dispatch({ 
          type: 'setStep', 
          payload: parseInt(e.target.value) 
        })}
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

17. Forward refs for component APIs

Use forwardRef to expose DOM refs or imperative methods from your components.

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

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

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

// Parent component can now call fancyInputRef.current.focus()
Enter fullscreen mode Exit fullscreen mode

18. Use ErrorBoundary for graceful error handling

Catch JavaScript errors anywhere in the component tree and display a fallback UI instead of crashing the entire app.

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h2>Something went wrong. Please refresh the page.</h2>;
    }

    return this.props.children;
  }
}

// Wrap your app or specific sections
<ErrorBoundary>
  <App />
</ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

19. Optimize context with split contexts

Prevent unnecessary re-renders by splitting frequently changing values from stable ones into separate contexts.

// Instead of one large context
const AppContext = React.createContext();

// Split into logical groups
const UserContext = React.createContext();
const ThemeContext = React.createContext();
const SettingsContext = React.createContext();

// Components only re-render when their specific context changes
Enter fullscreen mode Exit fullscreen mode

20. Use Suspense for data fetching

Combine Suspense with data fetching libraries that support it for cleaner loading states.

// With a Suspense-enabled data fetching library
function UserProfile({ userId }) {
  const user = useFetch(`/api/users/${userId}`); // Throws promise while loading

  return <div>{user.name}</div>;
}

// Parent handles loading state
<Suspense fallback={<ProfileSkeleton />}>
  <UserProfile userId={1} />
</Suspense>
Enter fullscreen mode Exit fullscreen mode

Wrapping up

These patterns have been invaluable while building UserJot and handling everything from complex state management to performance optimizations. Many of these tricks came from real problems I faced, like managing complex feedback board states, handling real-time updates, and keeping the UI responsive as data scales.

If you're building a SaaS product and need a way to collect user feedback, manage your roadmap, or keep users updated with changelogs, check out UserJot. It's built using many of these patterns, and I'm always discovering new ones as the platform grows.

UserJot feedback board with user discussions

Google AI Education track image

Build Apps with Google AI Studio 🧱

This track will guide you through Google AI Studio's new "Build apps with Gemini" feature, where you can turn a simple text prompt into a fully functional, deployed web application in minutes.

Read more →

Top comments (2)

Collapse
 
duncan_true profile image
Dun

Thank you for sharing this!

Collapse
 
ostap profile image
Ostap Brehin • Edited

And that's why I use Vue 👀 📗

MongoDB Atlas runs apps anywhere. Try it now.

MongoDB Atlas runs apps anywhere. Try it now.

MongoDB Atlas lets you build and run modern apps anywhere—across AWS, Azure, and Google Cloud. With availability in 115+ regions, deploy near users, meet compliance, and scale confidently worldwide.

Start Free

👋 Kindness is contagious

Explore this practical breakdown on DEV’s open platform, where developers from every background come together to push boundaries. No matter your experience, your viewpoint enriches the conversation.

Dropping a simple “thank you” or question in the comments goes a long way in supporting authors—your feedback helps ideas evolve.

At DEV, shared discovery drives progress and builds lasting bonds. If this post resonated, a quick nod of appreciation can make all the difference.

Okay