React Tutorial 0/120 lessons ~6 min read Lesson 14

    React Best Practices

    React Best Practices covers focused components, state ownership, pure rendering, effects, data flow, accessibility, security, testing, and evidence-based performance optimization.

    Course progress0%
    Focus
    93 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Learning Objectives

    By the end of this lesson, you will be able to:

    1. Understand what makes React code maintainable.
    2. Structure components effectively.
    3. Keep components small and focused.
    4. Follow proper state-management practices.
    5. Understand state ownership and colocation.
    6. Avoid unnecessary state.
    7. Keep rendering logic pure.
    8. Use props effectively.
    9. Avoid prop drilling where it becomes problematic.
    10. Handle lists and keys correctly.
    11. Write clean JSX.
    12. Handle events correctly.
    13. Use effects only when necessary.
    14. Avoid common useEffect mistakes.
    15. Design reusable components without over-engineering.
    16. Optimize rendering based on evidence.
    17. Handle loading, error, empty, and success states.
    18. Organize React applications by feature.
    19. Improve accessibility and maintainability.
    20. Apply senior-level React engineering practices.

    Introduction

    We have now learned the core React flow:

    text
    React
    ↓
    Vite
    ↓
    Project Structure
    ↓
    SPA
    ↓
    Developer Tools
    ↓
    JSX
    ↓
    Rendering UI

    At this point, you can write React code.

    But there's a much bigger question:

    How do we write React code that remains clean, scalable, performant, testable, and maintainable as the application grows?

    A beginner can write:

    tsx
    function App() {
    // everything here
    }

    A senior engineer thinks about:

    text
    Architecture
    State ownership
    Component boundaries
    Data flow
    Performance
    Accessibility
    Testing
    Maintainability
    Security

    That's where React Best Practices become important.

    What Are React Best Practices?

    React best practices are engineering principles that help us build applications that are:

    text
    Readable
    Maintainable
    Predictable
    Reusable
    Testable
    Accessible
    Performant
    Scalable

    There isn't one magical React architecture that works for every application.

    Instead, good React engineering means making appropriate decisions based on:

    text
    Application size
    Team size
    Business requirements
    Performance requirements
    Data complexity
    Deployment model

    The Golden Rule

    One of the most useful principles is:

    Keep things simple until complexity is justified.

    Don't create:

    text
    15 abstractions
    10 custom hooks
    8 context providers
    6 state-management libraries

    for a simple page.

    Instead:

    text
    Simple problem
    ↓
    Simple solution

    When complexity genuinely appears:

    text
    Growing problem
    ↓
    Measure
    ↓
    Identify complexity
    ↓
    Introduce appropriate abstraction

    This prevents over-engineering.

    Component Responsibility

    A component should ideally have a clear responsibility.

    For example:

    tsx
    function CourseCard() {
    // display one course
    }

    is easier to understand than:

    tsx
    function CoursePage() {
    // fetch courses
    // authenticate user
    // process payments
    // manage filters
    // render charts
    // handle notifications
    // manage modals
    // format invoices
    }

    The second component has too many responsibilities.

    Single Responsibility

    A useful principle is:

    A component should have a focused reason to change.

    For example:

    text
    CoursePage
    │
    ├── CourseFilters
    ├── CourseList
    ├── CourseCard
    └── Pagination

    Each component has a clearer responsibility.

    Avoid Giant Components

    This is a common enterprise problem.

    You may eventually see:

    text
    CourseDashboard.tsx
    --------------------
    1,500 lines

    containing:

    text
    API calls
    State
    Business logic
    Forms
    Tables
    Modals
    Charts
    Validation
    JSX

    This becomes difficult to:

    text
    Understand
    Test
    Debug
    Review
    Modify
    Reuse

    Instead, decompose the feature.

    Good Component Decomposition

    Instead of:

    text
    CourseDashboard

    consider:

    text
    CourseDashboard
    ├── CourseHeader
    ├── CourseFilters
    ├── CourseSummary
    ├── CourseList
    │ └── CourseCard
    ├── CoursePagination
    └── CourseDetailsModal

    The goal isn't to create the maximum number of components.

    The goal is:

    Create meaningful boundaries.

    Don't Over-Componentize

    The opposite problem also exists.

    You don't need:

    tsx
    function CourseTitle() {
    return <h2>React</h2>;
    }

    if the component provides no meaningful reuse, behavior, or boundary.

    Over-componentization can make code harder to follow:

    text
    Page
    ↓
    Section
    ↓
    Container
    ↓
    Wrapper
    ↓
    Content
    ↓
    Title

    without any real architectural benefit.

    Use components where they improve:

    text
    Reuse
    Readability
    Responsibility
    Testing
    State isolation

    Keep Rendering Pure

    A React component should generally behave like:

    text
    UI = f(props, state)

    For the same inputs, it should produce the same UI.

    Good:

    tsx
    function Greeting({
    name
    }: {
    name: string;
    }) {
    return <h1>Hello {name}</h1>;
    }

    Avoid side effects directly in render:

    tsx
    function Greeting() {
    localStorage.setItem("visited", "true");
    return <h1>Hello</h1>;
    }

    Rendering should describe UI, not perform unrelated external operations.

    Don't Mutate State Directly

    This is one of the most important React practices.

    Avoid:

    tsx
    const [user, setUser] = useState({
    name: "Jagannath"
    });
    user.name = "Rahul";
    setUser(user);

    Instead:

    tsx
    setUser({
    ...user,
    name: "Rahul"
    });

    You're creating a new object.

    The mental model:

    text
    Old State
    ↓
    Create New State
    ↓
    State Update
    ↓
    Render

    Why Immutability Matters

    Consider:

    tsx
    const [courses, setCourses] = useState<Course[]>([]);

    Avoid:

    tsx
    courses.push(newCourse);
    setCourses(courses);

    Prefer:

    tsx
    setCourses([
    ...courses,
    newCourse
    ]);

    This makes state transitions explicit.

    It also works better with React's rendering model and common optimization techniques.

    Keep State Minimal

    A common mistake is storing values that can be calculated.

    Bad:

    tsx
    const [firstName, setFirstName] = useState("John");
    const [lastName, setLastName] = useState("Doe");
    const [fullName, setFullName] = useState("John Doe");

    fullName is derived from:

    text
    firstName
    +
    lastName

    You can calculate:

    tsx
    const fullName =
    `${firstName} ${lastName}`;

    Now there is only one source of truth for the actual data.

    Avoid Redundant State

    Another example:

    tsx
    const [courses, setCourses] = useState([]);
    const [courseCount, setCourseCount] = useState(0);

    If:

    text
    courseCount = courses.length

    you probably don't need separate state.

    Instead:

    tsx
    const courseCount = courses.length;

    This avoids synchronization problems.

    One Source of Truth

    Prefer:

    text
    Single source of truth

    instead of:

    text
    Same information
    ↓
    Stored in multiple places

    For example:

    tsx
    const [cartItems, setCartItems] =
    useState<CartItem[]>([]);

    Then derive:

    tsx
    const totalItems = cartItems.length;

    and:

    tsx
    const totalPrice =
    cartItems.reduce(
    (sum, item) =>
    sum + item.price * item.quantity,
    0
    );

    This is safer than maintaining:

    text
    cartItems
    totalItems
    totalPrice

    as separate state values.

    State Colocation

    Keep state as close as possible to where it is used.

    Suppose only CourseFilters needs:

    text
    search
    sort
    category

    Don't automatically put them in global state.

    Prefer:

    text
    CoursePage
    │
    └── CourseFilters
    ├── search
    ├── sort
    └── category

    This is called state colocation.

    Why State Colocation Matters

    If state is unnecessarily high:

    text
    App
    └── Global State
    └── search
    ↓
    CoursePage
    ↓
    CourseFilters

    many components may become connected to state they don't actually need.

    Keeping state closer to its consumers can reduce:

    text
    Coupling
    Complexity
    Unnecessary renders

    Lift State When Necessary

    State shouldn't always stay local.

    Suppose two sibling components need the same state:

    text
    CourseFilters
    CourseList

    Then move the state to their closest common parent:

    text
    CoursePage
    ├── CourseFilters
    └── CourseList

    Now:

    text
    CoursePage
    │
    └── search state
    │
    ├── CourseFilters
    └── CourseList

    This is the classic:

    Lift state up

    pattern.

    Don't Make Everything Global

    Global state is useful for genuinely shared concerns.

    Examples might include:

    text
    Authenticated user
    Theme
    Locale
    Application-wide preferences
    Shared workflow state

    But don't put every piece of state into global state.

    For example:

    text
    Modal open/closed
    Input text
    Dropdown state
    Local tab selection

    usually doesn't need global state.

    Props Are the Default Data Flow

    React's primary data flow is:

    text
    Parent
    ↓
    Props
    ↓
    Child

    Example:

    tsx
    <CourseCard
    course={course}
    />

    This is explicit and easy to reason about.

    Prefer props when the data is naturally local to the component hierarchy.

    Avoid Unnecessary Prop Drilling

    Suppose:

    text
    App
    ↓
    Dashboard
    ↓
    CoursePage
    ↓
    CourseList
    ↓
    CourseCard

    and you pass:

    text
    user

    through every component only so CourseCard can access it.

    That's prop drilling.

    Sometimes it is perfectly acceptable.

    But when it becomes excessive, consider:

    text
    Context
    Feature-level state
    State management
    Better component boundaries

    Don't introduce Context just because two components share one value.

    Context Is Not Automatically Global State

    Context is useful for making a value available to descendants without explicitly passing props through every intermediate component.

    Examples:

    text
    Theme
    Locale
    Authentication context
    Feature configuration

    But Context has trade-offs.

    When a context value changes, consumers of that context may need to re-render.

    Therefore:

    Use Context intentionally, not as a universal state-management solution.

    Avoid Excessive useEffect

    One of the most important modern React practices is:

    Don't use an Effect for something you can calculate during rendering.

    Bad:

    tsx
    const [fullName, setFullName] =
    useState("");
    useEffect(() => {
    setFullName(
    `${firstName} ${lastName}`
    );
    }, [firstName, lastName]);

    This introduces unnecessary state and an unnecessary effect.

    Better:

    tsx
    const fullName =
    `${firstName} ${lastName}`;

    When Should You Use Effects?

    Effects are primarily for synchronizing React with something outside React.

    Examples:

    text
    Browser APIs
    Subscriptions
    Timers
    External widgets
    Network synchronization
    Non-React systems

    Think:

    text
    React
    ↕
    External System

    rather than:

    text
    React state
    ↓
    Another React state

    The second case often doesn't need an Effect.

    Avoid Effect Chains

    Suppose:

    text
    State A
    ↓
    Effect
    ↓
    State B
    ↓
    Effect
    ↓
    State C

    This can become difficult to reason about.

    Prefer deriving values directly when possible:

    text
    State
    ↓
    Derived Data
    ↓
    UI

    rather than:

    text
    State
    ↓
    Effect
    ↓
    State
    ↓
    Effect
    ↓
    State

    Don't Fetch Data Blindly in Every Component

    A large application can become messy if every component independently manages:

    text
    Loading
    API call
    Error
    Retry
    Caching
    Race conditions

    For example:

    text
    CourseCard
    → API
    CourseList
    → API
    CoursePage
    → API
    Dashboard
    → API

    You can end up with duplicated network requests.

    Prefer a deliberate data-fetching architecture using:

    text
    Feature services
    Custom hooks
    Server-state libraries
    Caching
    Route loaders
    Framework capabilities

    depending on your application.

    Separate UI From Business Logic When Appropriate

    Consider:

    tsx
    function CoursePage() {
    // 200 lines of pricing logic
    // 100 lines of validation
    // 100 lines of API handling
    // JSX
    }

    This becomes difficult to maintain.

    Instead:

    text
    CoursePage
    │
    ├── useCourses()
    ├── courseService
    ├── pricing utilities
    ├── validation
    └── UI components

    The exact architecture depends on complexity.

    Feature-Based Organization

    For large applications, organizing by feature is often easier to scale than putting everything into global type folders.

    Instead of:

    text
    components/
    hooks/
    services/
    types/
    utils/

    with hundreds of unrelated files, consider:

    text
    features/
    ├── courses/
    │ ├── components/
    │ ├── hooks/
    │ ├── services/
    │ ├── types/
    │ └── utils/
    │
    ├── authentication/
    │ ├── components/
    │ ├── hooks/
    │ ├── services/
    │ └── types/
    │
    └── payments/
    ├── components/
    ├── hooks/
    ├── services/
    └── types/

    This keeps related code together.

    Example Feature Architecture

    For a course platform:

    text
    src/
    └── features/
    └── courses/
    ├── components/
    │ ├── CourseCard.tsx
    │ ├── CourseList.tsx
    │ ├── CourseFilters.tsx
    │ └── CourseDetails.tsx
    │
    ├── hooks/
    │ └── useCourses.ts
    │
    ├── services/
    │ └── courseService.ts
    │
    ├── types/
    │ └── course.types.ts
    │
    └── utils/
    └── courseHelpers.ts

    This scales much better for large teams.

    Keep API Calls Out of Presentational Components

    Instead of:

    tsx
    function CourseCard() {
    // fetch course
    // update state
    // render course
    }

    prefer:

    text
    Data layer
    ↓
    Container / hook
    ↓
    CourseCard

    For example:

    tsx
    function CoursePage() {
    const { courses } = useCourses();
    return (
    <CourseList
    courses={courses}
    />
    );
    }

    Then:

    tsx
    function CourseCard({
    course
    }: Props) {
    return (
    <article>
    {course.title}
    </article>
    );
    }

    The card becomes easier to reuse and test.

    But Don't Over-Enforce "Container vs Presentational"

    You don't need to split every component into:

    text
    Container
    Presentational

    The architecture should follow actual complexity.

    For small components:

    text
    CourseCard

    can simply handle its own display.

    For complex features:

    text
    CoursePage
    ↓
    useCourses()
    ↓
    CourseList
    ↓
    CourseCard

    can provide clearer separation.

    Use judgment.

    Use Semantic HTML

    Don't use:

    tsx
    <div onClick={handleClick}>
    Save
    </div>

    when you really need a button.

    Prefer:

    tsx
    <button onClick={handleClick}>
    Save
    </button>

    Semantic HTML provides:

    text
    Accessibility
    Keyboard behavior
    Browser semantics
    Better maintainability

    for free.

    Accessibility Is a Best Practice

    React doesn't automatically make an application accessible.

    Think about:

    text
    Semantic HTML
    Keyboard navigation
    Labels
    Focus
    ARIA where appropriate
    Color contrast
    Screen readers
    Error messages

    For example:

    tsx
    <label htmlFor="email">
    Email
    </label>
    <input
    id="email"
    type="email"
    />

    is better than:

    tsx
    <div>Email</div>
    <input />

    Forms Should Be Accessible

    Good:

    tsx
    <label htmlFor="course-search">
    Search courses
    </label>
    <input
    id="course-search"
    type="search"
    value={search}
    onChange={e =>
    setSearch(e.target.value)
    }
    />

    This gives the input a meaningful accessible label.

    Use Stable Keys

    When rendering:

    tsx
    {courses.map(course => (
    <CourseCard
    key={course.id}
    course={course}
    />
    ))}

    prefer:

    text
    Stable unique identity

    Avoid:

    tsx
    key={Math.random()}

    because the key changes on every render.

    Also avoid array indexes when items can be reordered or removed.

    Don't Use Random Keys

    Bad:

    tsx
    <CourseCard
    key={Math.random()}
    course={course}
    />

    This can cause React to treat the component as a new instance repeatedly.

    That can lead to:

    text
    Lost state
    Unnecessary DOM work
    Unexpected behavior

    Use stable identifiers.

    Keep Keys Unique Among Siblings

    Good:

    tsx
    {courses.map(course => (
    <CourseCard
    key={course.id}
    course={course}
    />
    ))}

    Keys only need to be unique within the relevant sibling set.

    You don't need globally unique keys across the entire application.

    Avoid Premature Optimization

    A common mistake:

    text
    React component
    ↓
    Add memo
    ↓
    Add useMemo
    ↓
    Add useCallback
    ↓
    Add context
    ↓
    Add state library

    without measuring anything.

    Instead:

    text
    Build
    ↓
    Measure
    ↓
    Identify bottleneck
    ↓
    Optimize
    ↓
    Measure again

    This is a much stronger engineering approach.

    React.memo

    React.memo can prevent unnecessary rendering of a component when its props haven't changed according to its comparison.

    Example:

    tsx
    const CourseCard = memo(
    function CourseCard({
    course
    }: Props) {
    return (
    <article>
    {course.title}
    </article>
    );
    }
    );

    But don't automatically wrap every component in memo.

    Use it where it provides measurable value.

    useMemo

    useMemo can cache the result of an expensive calculation between renders when dependencies remain unchanged.

    Example:

    tsx
    const filteredCourses = useMemo(() => {
    return courses.filter(course =>
    course.title
    .toLowerCase()
    .includes(search.toLowerCase())
    );
    }, [courses, search]);

    But if the calculation is trivial:

    tsx
    const fullName =
    `${firstName} ${lastName}`;

    you don't need useMemo.

    useCallback

    useCallback can preserve function identity between renders.

    For example:

    tsx
    const handleSelect = useCallback(
    (id: string) => {
    selectCourse(id);
    },
    [selectCourse]
    );

    It can be useful when function identity affects memoized children or hook dependencies.

    But again:

    Don't use useCallback everywhere by default.

    Avoid Premature Memoization

    Memoization adds complexity.

    If:

    text
    Calculation = 0.01 ms

    and you spend time optimizing it without evidence, you've potentially made the code harder to understand for no meaningful benefit.

    Optimization should be driven by:

    text
    Measurements
    User experience
    Application scale
    Actual bottlenecks

    Keep State Local for Performance

    Suppose:

    text
    App
    ├── Header
    ├── Sidebar
    ├── Dashboard
    └── CourseFilter

    If CourseFilter owns:

    text
    searchText

    only its relevant subtree needs to respond to it.

    If App owns it unnecessarily, changing the value may involve a much larger rendering path.

    State colocation can therefore improve both architecture and performance.

    Avoid Unnecessary Context Updates

    Suppose:

    tsx
    <AuthContext.Provider
    value={{
    user,
    logout
    }}
    >

    If the provider creates a new object every render, consumers may receive a new context value.

    Depending on the architecture, this can cause additional consumer updates.

    This is one reason context design and value identity matter.

    But don't blindly memoize context values without understanding the actual performance issue.

    Handle All UI States

    A production component shouldn't assume:

    text
    Data always exists
    API always succeeds
    User always behaves correctly

    Think:

    text
    Loading
    ↓
    Error
    ↓
    Empty
    ↓
    Success

    Example:

    tsx
    if (loading) {
    return <Loading />;
    }
    if (error) {
    return <ErrorMessage />;
    }
    if (courses.length === 0) {
    return <EmptyState />;
    }
    return <CourseList courses={courses} />;

    This makes applications much more robust.

    Don't Ignore Error Handling

    Bad:

    tsx
    try {
    await saveCourse(course);
    } catch {
    // nothing
    }

    The user may have no idea that the operation failed.

    Instead:

    text
    API Failure
    ↓
    Application Error Handling
    ↓
    User-Friendly Message

    For example:

    text
    "Unable to save the course.
    Please try again."

    Avoid exposing internal implementation details to users.

    Handle Loading States Carefully

    Don't always show:

    text
    Loading...

    for every tiny update.

    For example, if a table is already displayed and a filter changes, completely replacing it with a blank loading screen may create a poor user experience.

    Depending on the UX, consider:

    text
    Existing data
    +
    Loading indicator

    instead.

    The right approach depends on the interaction.

    Avoid Unnecessary API Requests

    Suppose the user types:

    text
    r
    re
    rea
    reac
    react

    and each keystroke triggers an API request.

    You may create:

    text
    5 requests

    instead of one intentional request.

    For search fields, consider:

    text
    Debouncing
    Caching
    Server-side search
    Request cancellation

    where appropriate.

    Race Conditions

    Suppose:

    text
    Request A → "react"
    Request B → "java"

    Request B finishes first.

    Then Request A finishes later.

    If you aren't careful:

    text
    Older response
    ↓
    Overwrites newer data

    This creates a stale UI.

    Good data-fetching architecture should account for:

    text
    Cancellation
    Request identity
    Caching
    Race conditions

    Don't Put Secrets in React

    This is critical.

    Never put:

    text
    Database password
    Private API key
    Secret token
    Cloud credentials

    directly into frontend code.

    Anything bundled into a browser application can potentially be inspected.

    Remember:

    text
    Browser
    ↓
    User can inspect

    Frontend configuration is not secret storage.

    Environment Variables Are Not Automatically Secret

    Developers sometimes think:

    text
    VITE_SECRET_KEY

    means:

    It's secret.

    Not if it is included in the client bundle.

    For example:

    text
    Frontend environment variable
    ↓
    Build
    ↓
    JavaScript bundle
    ↓
    Browser

    Users can potentially inspect it.

    Use backend services for actual secrets.

    Validate on the Backend

    Frontend validation is useful for UX:

    text
    Email format
    Required field
    Password length

    But never rely solely on frontend validation for security.

    Correct architecture:

    text
    Frontend validation
    ↓
    Better user experience
    Backend validation
    ↓
    Actual enforcement

    Keep Business Rules Centralized

    Suppose a course discount is:

    text
    20%

    Don't duplicate:

    text
    CoursePage
    CourseCard
    Checkout
    Invoice

    with slightly different logic.

    Prefer a shared business rule/service when appropriate:

    text
    Pricing logic
    ↓
    Single source of truth

    This prevents inconsistent behavior.

    Reusable Components Should Be Generic Enough

    Suppose you create:

    tsx
    <CourseCard />

    with 25 props:

    text
    title
    subtitle
    price
    discount
    image
    badge
    rating
    author
    category
    ...

    You may have created a component that tries to solve everything.

    Prefer meaningful boundaries.

    Sometimes:

    text
    CourseCard

    should simply receive:

    tsx
    <CourseCard course={course} />

    and the component can decide how to display the course.

    Don't Build a "God Component"

    A God component knows:

    text
    Everything

    Avoid:

    text
    App.tsx
    ├── Authentication
    ├── Routing
    ├── API calls
    ├── Business rules
    ├── Analytics
    ├── Payments
    ├── Courses
    ├── Reports
    └── 1000 lines JSX

    Instead:

    text
    App
    ├── Providers
    ├── Router
    └── Application Features

    Then features own their complexity.

    Keep Business Logic Testable

    Instead of putting everything inside JSX:

    tsx
    const finalPrice =
    price -
    price * discount / 100;

    for simple calculations it's fine.

    For complex pricing:

    text
    Discount rules
    Tax
    Coupons
    Membership
    Currency
    Regional pricing

    extract the logic:

    tsx
    calculateFinalPrice(...)

    Now you can unit-test it independently.

    Custom Hooks

    Custom hooks are useful when behavior needs to be reused.

    For example:

    tsx
    function useCourses() {
    // fetch courses
    // manage loading
    // manage error
    // return data
    }

    Then:

    tsx
    function CoursePage() {
    const {
    courses,
    loading,
    error
    } = useCourses();
    // UI
    }

    This separates reusable behavior from UI structure.

    Don't Create Hooks Just to Rename Code

    Bad abstraction:

    tsx
    function useTitle() {
    return "React";
    }

    if it's used once and provides no meaningful behavior.

    Good custom hook:

    text
    useCourses()
    useAuth()
    useDebouncedValue()
    usePagination()

    when the behavior is reusable or meaningfully encapsulated.

    Keep Custom Hooks Focused

    Avoid:

    text
    useEverything()

    that manages:

    text
    Authentication
    Courses
    Payments
    Notifications
    Theme
    Analytics

    Instead:

    text
    useAuth()
    useCourses()
    usePayments()
    useNotifications()

    Focused hooks are easier to understand and test.

    Prefer Composition Over Complex Inheritance

    React is designed around composition.

    For example:

    tsx
    <Card>
    <CourseDetails />
    </Card>

    rather than trying to build a deep inheritance hierarchy.

    Composition gives you flexibility:

    text
    Card
    ├── CourseDetails
    ├── UserDetails
    └── PaymentDetails

    depending on the context.

    Use Clear Naming

    Prefer:

    text
    isLoading
    isAuthenticated
    handleSubmit
    handleDelete
    selectedCourse
    filteredCourses

    over:

    text
    flag
    data1
    temp
    thing
    handleStuff

    Good naming reduces the amount of code you need to understand.

    Boolean Naming

    Boolean values should read naturally.

    Good:

    tsx
    const isLoading = true;
    const isAdmin = false;
    const hasCourses = true;
    const canEdit = true;

    Avoid:

    tsx
    const loading = true;
    const admin = false;
    const edit = true;

    The latter isn't always wrong, but the is/has/can/should convention makes intent clearer.

    Event Handler Naming

    Prefer:

    text
    handleSubmit
    handleClick
    handleDelete
    handleSearch
    handleCourseSelect

    For props:

    text
    onSubmit
    onClick
    onDelete
    onCourseSelect

    This creates a useful convention:

    text
    Parent
    ↓
    onCourseSelect
    Child
    ↓
    handleCourseSelect

    Keep JSX Readable

    Avoid:

    tsx
    <div className="course-card"><h2>{course.title}</h2><p>{course.description}</p><button onClick={() => selectCourse(course.id)}>View</button></div>

    Prefer:

    tsx
    <div className="course-card">
    <h2>{course.title}</h2>
    <p>
    {course.description}
    </p>
    <button
    onClick={() =>
    selectCourse(course.id)
    }
    >
    View
    </button>
    </div>

    Readable code is easier to review and debug.

    Avoid Deeply Nested JSX

    If your JSX becomes:

    text
    <div>
    <section>
    <div>
    <div>
    <article>
    <div>
    ...

    consider whether component boundaries or semantic elements can make the structure clearer.

    Use Semantic Elements

    Prefer:

    tsx
    <header />
    <nav />
    <main />
    <section />
    <article />
    <footer />
    <button />
    <form />

    when appropriate.

    This improves:

    text
    Accessibility
    SEO
    Readability
    Maintainability

    Keep Accessibility in the Design

    Don't wait until the end of the project.

    Think about:

    text
    Keyboard
    Focus
    Labels
    Semantics
    Screen readers
    Error messaging
    Loading announcements

    from the beginning.

    Accessibility is much easier when it is built into component design.

    Test Behavior, Not Implementation

    When testing a React component, prefer testing:

    text
    User clicks button
    ↓
    Expected UI appears

    rather than testing internal implementation details such as:

    text
    Internal function X was called exactly twice

    Behavior-oriented tests tend to survive refactoring better.

    Testing Pyramid

    A mature application may use:

    text
    E2E
    / \
    Integration
    / \
    Unit Tests

    Examples:

    Unit

    text
    calculatePrice()

    Integration

    text
    CoursePage + API mocking

    E2E

    text
    User logs in
    ↓
    Searches course
    ↓
    Opens course
    ↓
    Enrolls

    The right balance depends on the application.

    Don't Ignore Error and Loading Tests

    If a component has:

    text
    Loading
    Error
    Empty
    Success

    test meaningful states.

    For example:

    text
    API success
    API failure
    No courses
    Slow response

    Production bugs often happen in states developers didn't test.

    Performance Best Practice

    Don't say:

    React is slow.

    Instead ask:

    text
    What is slow?

    Possible causes:

    text
    React render
    JavaScript computation
    API
    Network
    Large list
    DOM size
    Browser layout
    Image size
    Third-party library

    Use the correct diagnostic tool.

    Performance Investigation Model

    text
    User reports slowness
    ↓
    Reproduce
    ↓
    Measure
    ↓
    Classify bottleneck
    │
    ┌──────┼────────┐
    ▼ ▼ ▼
    React API Browser
    │ │ │
    Profiler Network Performance
    │
    ▼
    Root Cause
    ↓
    Optimize
    ↓
    Measure Again

    This is a senior engineering approach.

    Don't Optimize Without Evidence

    Avoid statements like:

    Let's add useMemo.

    Instead:

    The profiler shows this expensive calculation runs on every render even though its dependencies rarely change, so useMemo may be appropriate.

    That's the difference between:

    text
    Keyword-driven development

    and:

    text
    Evidence-driven engineering

    Use Code Splitting for Large Applications

    A large application shouldn't necessarily load everything immediately.

    Conceptually:

    text
    Initial Bundle
    ↓
    Dashboard
    ↓
    Load only what's needed

    Other features:

    text
    Reports
    Admin
    Analytics
    Settings

    can potentially be loaded later.

    We'll study:

    text
    lazy()
    Suspense
    Route-level code splitting

    later.

    Optimize Images and Assets

    Frontend performance isn't only React.

    Large images can dominate loading time.

    Prefer:

    text
    Optimized image formats
    Responsive images
    Lazy loading
    Proper dimensions
    CDN
    Caching

    React is only one part of the application's performance.

    Avoid Unnecessary DOM

    A simple:

    tsx
    <>
    <h1>React</h1>
    <p>Course</p>
    </>

    may be better than:

    tsx
    <div>
    <div>
    <div>
    <h1>React</h1>
    <p>Course</p>
    </div>
    </div>
    </div>

    Use the simplest structure that communicates the UI correctly.

    Security Best Practices

    React applications should also follow secure development practices.

    Important principles include:

    text
    Don't trust client input
    Don't expose secrets
    Validate on backend
    Escape/render untrusted content safely
    Use secure authentication
    Protect APIs
    Avoid dangerous HTML injection

    Be especially careful with:

    tsx
    dangerouslySetInnerHTML

    It should only be used with properly trusted/sanitized content.

    Don't Put Authorization Only in the UI

    This:

    tsx
    {isAdmin && <AdminButton />}

    is useful for UX.

    But it isn't security.

    Correct architecture:

    text
    Frontend
    ↓
    Hide unavailable actions
    Backend
    ↓
    Enforce authorization

    A user can modify frontend behavior in their browser.

    The backend must enforce permissions.

    Keep Dependencies Under Control

    Don't install a package for every small problem.

    Before adding:

    text
    library
    framework
    state manager
    utility

    ask:

    text
    Do we really need it?
    Can React/JavaScript already solve it?
    Will this increase maintenance?
    Does the team know it?

    Dependencies have long-term costs.

    Keep React and Dependencies Updated

    Modern React applications evolve.

    Keep an eye on:

    text
    React
    TypeScript
    Vite
    Testing libraries
    Build tools
    Security patches

    But don't blindly upgrade production dependencies without testing.

    Use:

    text
    Release notes
    Automated tests
    Staging
    Incremental upgrades

    for major changes.

    Code Review Checklist

    When reviewing React code, ask:

    Components

    text
    Is responsibility clear?
    Is it too large?
    Is it over-engineered?

    State

    text
    Is state necessary?
    Can it be derived?
    Is it colocated?
    Is it mutated?

    Effects

    text
    Is this Effect actually necessary?
    Could this be calculated during render?

    Performance

    text
    Is there evidence of a bottleneck?
    Are large lists handled correctly?

    Accessibility

    text
    Are semantic elements used?
    Are forms labeled?
    Is keyboard interaction supported?

    Security

    text
    Are secrets exposed?
    Is backend validation present?

    Maintainability

    text
    Are names clear?
    Is logic testable?
    Are abstractions justified?

    A Practical React Best-Practices Checklist

    Before committing React code:

    text
    □ Components have clear responsibilities
    □ State is minimal
    □ State is colocated where practical
    □ Derived values aren't unnecessarily stored
    □ State isn't mutated directly
    □ Effects are genuinely necessary
    □ JSX is readable
    □ List keys are stable
    □ Event handlers aren't accidentally executed during render
    □ Components use semantic HTML
    □ Forms have accessible labels
    □ Loading/error/empty states are handled
    □ Business logic is testable
    □ No secrets are exposed
    □ Backend authorization is enforced
    □ Performance optimizations are evidence-based
    □ Large lists are handled appropriately
    □ Naming is clear
    □ Tests focus on user-visible behavior
    □ Dependencies are justified

    Real-World Example

    Suppose we are building:

    text
    TechLearningPro

    with:

    text
    Courses
    Students
    Payments
    Authentication
    Admin
    Reports

    A scalable architecture might look like:

    text
    src/
    │
    ├── app/
    │ ├── router/
    │ ├── providers/
    │ └── App.tsx
    │
    ├── features/
    │ ├── auth/
    │ │ ├── components/
    │ │ ├── hooks/
    │ │ ├── services/
    │ │ └── types/
    │ │
    │ ├── courses/
    │ │ ├── components/
    │ │ ├── hooks/
    │ │ ├── services/
    │ │ ├── types/
    │ │ └── utils/
    │ │
    │ ├── payments/
    │ └── students/
    │
    ├── shared/
    │ ├── components/
    │ ├── hooks/
    │ ├── utils/
    │ └── types/
    │
    └── main.tsx

    The important thing isn't copying this exact structure.

    The important principle is:

    Organize code around responsibilities and features as complexity grows.

    Senior-Level React Engineering Mindset

    A junior developer may ask:

    How can I make this component work?

    A senior developer asks:

    What is the simplest maintainable design for this behavior?

    An experienced engineer also asks:

    text
    Who owns this state?
    Who owns this business rule?
    What happens when the API fails?
    What happens with 10,000 records?
    How will this be tested?
    Will this be accessible?
    What happens during concurrent user actions?
    What happens when requirements change?

    That mindset is more valuable than memorizing React APIs.

    Best-Practice Decision Framework

    When you're unsure how to implement something:

    text
    Requirement
    │
    ▼
    Simplest Design
    │
    ▼
    Is complexity justified?
    │ │
    No Yes
    │ │
    ▼ ▼
    Keep it Measure/
    simple Analyze
    │
    ▼
    Add abstraction
    │
    ▼
    Test it
    │
    ▼
    Monitor it

    This prevents unnecessary architecture.

    Interview questions & answers

    Interview preparation

    13 questions grouped by difficulty — expand any card for a model answer and follow-up probe.

    Beginner

    3
    1. 1What are React best practices?
      Beginner

      Model answer

      They are engineering practices that help make React applications maintainable, predictable, reusable, accessible, testable, and performant.

      Examples include keeping components focused, avoiding unnecessary state, using stable keys, keeping rendering pure, and handling UI states properly.

    2. 2Why shouldn't we mutate React state directly?
      Beginner

      Model answer

      Direct mutation can make state changes difficult for React and developers to reason about and can lead to stale or unexpected UI behavior.

      Creating new state values makes the state transition explicit.

    3. 3What is state colocation?
      Beginner

      Model answer

      State colocation means keeping state as close as practical to the components that actually use it instead of unnecessarily putting it into global state.

    Intermediate

    4
    1. 4When should you use Context?
      Intermediate

      Model answer

      I use Context when a value needs to be available to many components across a subtree and passing it through props would create unnecessary coupling or prop drilling.

      Typical examples are theme, locale, authentication information, or feature-level configuration.

    2. 5When should you use useEffect?
      Intermediate

      Model answer

      I use Effects primarily to synchronize React with external systems.

      I avoid using them for values that can be derived during rendering or for ordinary state-to-state transformations.

    3. 6When should you use useMemo?
      Intermediate

      Model answer

      I use useMemo when there is an expensive calculation and profiling shows that avoiding repeated calculation provides a meaningful benefit. I don't use it automatically for every derived value.
    4. 7How do you prevent unnecessary re-renders?
      Intermediate

      Model answer

      First I identify the actual rendering problem using profiling. Then I look at state placement, prop identity, context updates, component boundaries, expensive calculations, and large lists. Depending on the cause, I may use state colocation, React.memo, memoization, virtualization, or architectural changes.

    Advanced

    6
    1. 8What is more important: reusable components or simple components?
      Advanced

      Model answer

      Neither should be pursued blindly.

      I prefer simple, focused components first and introduce reuse when there is a genuine repeated behavior or meaningful abstraction.

      Over-abstraction can be just as harmful as duplication.

    2. 9How would you structure a large React application?
      Advanced

      Model answer

      For a large application, I'd generally organize around business features, with each feature owning its components, hooks, services, types, and utilities where appropriate.

      I'd keep shared infrastructure separate and establish clear boundaries between UI, state/data fetching, and business logic.

    3. 10How do you approach React performance?
      Advanced

      Model answer

      I use a measure-first approach.

      I'd reproduce the issue, profile the application, identify whether the bottleneck is rendering, computation, network, DOM size, or browser work, and then apply the smallest effective optimization.

      I'd validate the result with another measurement.

    4. 11How do you prevent a React application from becoming difficult to maintain?
      Advanced

      Model answer

      I focus on clear component boundaries, feature-based organization, minimal state, predictable data flow, limited global state, focused custom hooks, testable business logic, consistent conventions, and strong code review practices.

      I also avoid introducing abstractions until the complexity justifies them.

    5. 12How do you decide whether state should be local, lifted, contextual, or global?
      Advanced

      Model answer

      I start with ownership.

      If only one component needs it, I keep it local.

      If siblings need it, I lift it to their closest common owner.

      If many descendants need the same cross-cutting value, Context may be appropriate.

      For complex shared application state or server state, I'd evaluate an appropriate state or data-fetching solution based on the actual requirements.

    6. 13How do you balance performance and maintainability?
      Advanced

      Model answer

      I avoid optimizing prematurely.

      I start with a clean architecture and measure real performance problems.

      If a bottleneck appears, I choose the least complex optimization that addresses it and verify the improvement.

      This keeps performance work evidence-based without making the entire codebase harder to understand.

    Real Interview Scenario

    Interviewer:

    You have a React application with 100,000 records. How would you design it?

    Don't immediately answer:

    I'll use useMemo.

    A stronger answer:

    text
    100,000 records
    ↓
    Do we really need all records?
    ↓
    Server-side pagination/filtering
    ↓
    Efficient API
    ↓
    Client caching
    ↓
    Virtualized rendering
    ↓
    Render only visible rows

    Then investigate:

    text
    Network
    Memory
    Rendering
    DOM
    Browser performance

    This demonstrates architectural thinking.

    Another Interview Scenario

    Interviewer:

    A component has 15 useEffect hooks. Is that bad?

    Don't answer:

    Yes.

    Instead:

    Not necessarily. The number alone isn't the problem. I'd review what each Effect is synchronizing with. If many Effects are being used to derive state from other state, I'd consider simplifying them. If they're legitimately synchronizing with external systems, they may be justified.

    That is a much stronger senior-level answer.

    Another Scenario

    Interviewer:

    Should every component use React.memo?

    Answer:

    No. React.memo is an optimization, not a default component requirement. I'd use profiling to determine whether a component is rendering unnecessarily and whether memoization can meaningfully reduce the work.

    Another Scenario

    Interviewer:

    Should everything go into Redux or another global state manager?

    Answer:

    No. I prefer state ownership based on scope. Local state should remain local, shared state can be lifted or provided through Context when appropriate, and complex application or server state should use an appropriate solution based on the problem.

    The 10 Golden React Rules

    If you remember only ten rules from this lesson, remember these:

    Rule 1

    Keep components focused.

    Rule 2

    Keep state minimal.

    Rule 3

    Don't store what you can derive.

    Rule 4

    Don't mutate state directly.

    Rule 5

    Keep state close to where it's used.

    Rule 6

    Use Effects for external synchronization, not ordinary calculations.

    Rule 7

    Use stable keys for lists.

    Rule 8

    Optimize based on measurement, not assumptions.

    Rule 9

    Build accessibility and security into the architecture.

    Rule 10

    Prefer simple solutions until complexity is justified.

    Final Mental Model

    Think about React architecture like this:

    text
    APPLICATION
    │
    ┌──────────┴──────────┐
    ▼ ▼
    FEATURES SHARED
    │ │
    ▼ ▼
    Components Components
    Hooks Hooks
    Services Utilities
    Types
    │
    ▼
    STATE
    │
    ┌───────┴────────┐
    ▼ ▼
    Local Shared
    │ │
    └───────┬────────┘
    ▼
    JSX
    │
    ▼
    RENDER
    │
    ▼
    RECONCILIATION
    │
    ▼
    COMMIT
    │
    ▼
    DOM

    And around everything:

    text
    Accessibility
    Security
    Testing
    Performance
    Observability
    Maintainability

    Final Takeaway

    The biggest React best practice isn't a specific API.

    It is an engineering mindset:

    Build the simplest architecture that correctly solves today's problem, keep state and responsibilities close to where they belong, make rendering predictable, measure before optimizing, and introduce complexity only when the application genuinely needs it.

    A professional React developer doesn't just know:

    text
    useState
    useEffect
    useMemo
    useCallback

    They know when not to use them.

    That distinction is what separates writing React code from engineering React applications.

    Ready to mark this lesson complete?Track your journey across the entire course.