DEV Community

Cover image for Top 5 React essential techniques every beginner should know!
Khalid Edaoudi
Khalid Edaoudi

Posted on

Top 5 React essential techniques every beginner should know!

React has become one of the most widely adopted libraries for building user interfaces. Its component-based architecture, declarative style, and rich ecosystem make it a powerful tool for projects of any size. But writing good React code requires more than just knowing JSX, it requires understanding the core techniques that make React applications clean, performant, and maintainable.
Here are five techniques that form the backbone of professional React development.

1. Conditional Rendering

In any real application, what appears on screen depends on state, is the user logged in ? Has the data loaded ? Did an error occur ? Conditional rendering is how React handles these scenarios, and mastering it means your UI always reflects reality.
The simplest approach uses JavaScript's ternary operator directly inside JSX:

function Dashboard({ user }) {
  return (
    <div>
      {user ? <WelcomePanel name={user.name} /> : <LoginPrompt />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

For cases where you only want to render something or nothing, the logical && operator keeps things concise:

{hasNotifications && <NotificationBadge count={notifications.length} />}
Enter fullscreen mode Exit fullscreen mode

2. Lists and Keys

Rendering dynamic lists is something you'll do in nearly every React project, product catalogs, navigation menus, table rows. The pattern itself is straightforward: map over an array and return JSX for each item.

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

The critical detail here is the key prop. React uses keys to track which items have changed, been added, or been removed. When keys are stable and unique (like a database ID), React can efficiently update only the elements that actually changed instead of re-rendering the entire list.
A common mistake is using the array index as a key. This works fine for static lists that never reorder, but the moment items are sorted, filtered, or inserted, index-based keys cause subtle bugs — inputs lose their values, animations break, and components reset unexpectedly. The rule of thumb:

use a stable, unique identifier from your data whenever possible

3. Controlled Components

Forms are notoriously tricky in web development, and React's answer is the controlled component pattern. In a controlled component, the form element's value is driven by React state, and every change flows through a handler function.

function SearchBar() {
  const [query, setQuery] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    search(query);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={query}
        onChange={() => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <button type="submit">Go</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

This might feel like extra ceremony compared to just reading the DOM directly, but the benefits are significant. Because React owns the value at all times, you can instantly validate input, enforce formatting , disable a submit button until a form is valid, or synchronize multiple fields that depend on each other all without ever touching the DOM.

Controlled components embody one of React's core philosophies: the UI is a function of state. When state is the single source of truth, your forms become predictable and easy to debug.

4. Fetching Data

Most React applications need to communicate with a server, and data fetching is where many developers first encounter the interplay between effects and state. A typical pattern with useEffectlooks like this:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    e.preventDefault();
    async function fetchUser() {
      try {
        setLoading(true);
        const res = await fetch(`/api/users/${userId}`);
        if (!res.ok) throw new Error('Failed to fetch');
        const data = await res.json();
        setUser(data);
        setError(null);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUser();
  }, [userId]);

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage message={error} />;
  return <ProfileCard user={user} />;
}
Enter fullscreen mode Exit fullscreen mode

In production, most teams reach for a dedicated data-fetching library like React Query, TanStack Query (I will discuss TanStack library in another blog) or SWR. These handle caching, background refetching, pagination, and retry logic out of the box, letting you focus on your application logic instead of reinventing request management. Understanding the manual approach above is still valuable, though it teaches you why those libraries exist and what problems they solve.

5. State Management with Redux

As applications grow, managing state that's shared across many components becomes a real challenge. Props can be passed down one or two levels without issue, but when you find yourself threading the same data through five layers of components that don't even use it (prop drilling), it's time for a dedicated state management solution. Redux is the most established choice in the React ecosystem.

Props drilling

Redux is built on three principles:

  1. Single store holds the application state representing the single source of thruth.
  2. State is read-only and can only be changed by dispatching actions.
  3. Changes are made through pure functions called reducers.

Install Redux Toolkit:

npm install @reduxjs/toolkit
Enter fullscreen mode Exit fullscreen mode

Here's what a modern Redux setup looks like using Redux Toolkit, which is now the recommended way to write Redux:

StoreHook.ts

import {useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from '../redux/store'
import type { TypedUseSelectorHook } from 'react-redux'

export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
Enter fullscreen mode Exit fullscreen mode

This file provides typed hooks for interacting with the Redux store, built on top of React-Redux's useDispatch and useSelector. It exports useAppDispatch and useAppSelector. These hooks replace the default untyped versions, ensuring type safety when dispatching actions or selecting state in components. This is a best practice in TypeScript Redux setups, preventing runtime errors and improving developer experience by leveraging inferred types from the store configuration.

StateSlice.ts

import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'


interface States {
  state1: number,
  state2: number
}

const initialState: States = { state1: 0, state2:0 }

const stateSlice = createSlice({
    name:'state',
    initialState,
    reducers : {
        setNewState1: (state, action:PayloadAction<number>) => {
            state.state1 = action.payload
        },
        setNewState2: (state, action:PayloadAction<number>) => {
            state.state2 = action.payload
        }
    },
})

export const {setNewState1, setNewState2} = stateSlice.actions
export default stateSlice.reducer
Enter fullscreen mode Exit fullscreen mode

This file defines a Redux slice for managing application state using Redux Toolkit's createSlice. It establishes the structure and logic for two numeric states (state1 and state2), which are initialized to 0. The slice includes two reducers: setNewState1 and setNewState2, each accepting a number payload to update the respective state property. This slice encapsulates the state management logic, allowing components to dispatch actions to modify these values immutably. It's a core part of Redux's modular approach, separating concerns by grouping related state and actions into a single unit.

Store.ts

import { configureStore } from '@reduxjs/toolkit'
import stateReducer from "./StateSlice"

export const store = configureStore({
  reducer: {
    state: stateReducer
  },
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
Enter fullscreen mode Exit fullscreen mode

This file configures the Redux store using Redux Toolkit's configureStore. It combines multiple reducers into a single store object, enabling centralized state management across the app. The store is typed with RootState (inferred from the store's state) and AppDispatch (the store's dispatch function), which are exported for use in components and hooks. This setup ensures the store is ready to handle actions and provide state to the React app, promoting predictable state updates and debugging capabilities.

State1.tsx

import { useAppSelector } from "../../hooks/StoreHook";

export default function State1() {

    const state1 = useAppSelector((state) => state.state.state1)
  return (
    <div className="border cursor-default rounded-md font-bold bg-orange-400 hover:bg-orange-500 transition-colors w-fit py-5 px-8">
      <span className="font-normal">State1 : </span>{state1}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

State2.tsx

import { useAppSelector } from "../../hooks/StoreHook";

export default function State2() {
    const state2 = useAppSelector((state) => state.state.state2)
  return (
    <div className="border cursor-default hover:bg-green-500 font-bold transition-colors rounded-md bg-green-400 w-fit py-5 px-8">
      <span className="font-normal">State2 : </span>{state2}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Those React components displays the values of state1 & state2 from the Redux store using the useAppSelector hook. It selects the specific slice of state and renders it. The component is read-only, serving as a UI representation of the state without any update logic. they demonstrate how to connect a component to Redux for displaying data, keeping the UI reactive to state changes.

StateManagement.tsx

import { useState } from "react";
import State1 from "./reduxComponent/State1";
import State2 from "./reduxComponent/State2";
import { setNewState1, setNewState2 } from "../redux/StateSlice";
import { useAppDispatch } from "../hooks/StoreHook";

export default function StateManagement() {
  const [error, setError] = useState(false);
  const dispatch = useAppDispatch();
  const [state, setState] = useState(String);
  return (
    <div className="flex justify-center mt-5">
      <div className="w-1/3">
        <div className="flex justify-between">
          <input
            className={`
          w-96 px-4 py-2 border rounded-lg
          focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
          disabled:bg-gray-100 disabled:cursor-not-allowed
          transition-all duration-200
          ${error ? "border-red-500 focus:ring-red-500" : "border-gray-300"}
        `}
            type="text"
            value={state}
            onChange={(e) => setState(e.target.value)}
          />
          <button
            onClick={() => {
              dispatch(setNewState1(Number(state.split(" ")[0])));
              dispatch(setNewState2(Number(state.split(" ")[1])));
            }}
            className="px-5 border rounded-md hover:bg-blue-600 transition-colors bg-blue-400 text-white"
          >
            Update
          </button>
        </div>
        <div className="flex justify-between mt-8">
          <State1 />
          <State2 />
        </div>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

This page component integrates Redux for state management and user interaction. It uses useAppDispatch to dispatch actions (setNewState1 and setNewState2) based on user input from a text field. The input expects space-separated numbers (e.g., "10 20"), which are parsed and updated in the store via the "Update" button. It also renders State1 and State2 components to show the current state values.

NOTE: we didn't pass any props to State1 & State2 Components, now all the states are handled by Redux.

Demo1


Demo2

Now we had been demonstrates a complete Redux flow: user input triggers actions, reducers update state, and components re-render to reflect changes, all while handling potential errors (though the error state isn't fully utilized here).

Top comments (0)