React Part 2—Complete Guide to Props, Conditional Rendering, Hooks & State Management

React is one of the most in-demand frontend libraries, and mastering its core concepts is essential for building scalable and high-performance web applications.

In React Part 2, based on the React Part 2 YouTube lecture by me, we dive deeper into real-world React concepts that every developer must know.

This guide covers Props, Conditional Rendering, Rendering Lists, Lifting State Up, useRef, and useEffect with clear explanations and practical examples.

Props (Properties)

Props are like arguments you pass to a function, but for components! They allow you to pass data from parent components to child components, making your components dynamic and reusable.

Real-world example: Think of a product card on Amazon—the same card component is reused for every product, but with different props (product name, price, image, etc.).

Basic Example:

// Child Component
function UserCard(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>Age: {props.age}</p>
      <p>Email: {props.email}</p>
    </div>
  );
}

// Parent Component
function App() {
  return (
    <div>
      <UserCard name="Kartik Mathur" age={28} email="[email protected]" />
      <UserCard name="Nitesh" age={24} email="[email protected]" />
    </div>
  );
}

Destructuring Props (Cleaner Way):

  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Email: {email}</p>
    </div>
  );
}```

Key Points:

  • Props are read-only (immutable)—you cannot modify them inside the child component
  • Props flow one way: from parent to child
  • You can pass any data type: strings, numbers, arrays, objects, functions
  • Use destructuring for cleaner code

Conditional Rendering

Conditional rendering allows you to display different content based on certain conditions. It's like using if-else statements for your UI!

Method 1: Using if-else

  if (isLoggedIn) {
    return <h1>Welcome back!</h1>;
  } else {
    return <h1>Please sign in.</h1>;
  }
}```

Method 2: Using Ternary Operator (Most Common)

  return (
    <div>
      {isLoggedIn ? (
        <h1>Welcome back!</h1>
      ) : (
        <h1>Please sign in.</h1>
      )}
    </div>
  );
}```

Method 3: Using && and || Operator (For single condition)

  return (
    <div>
      <h1>Your Dashboard</h1>
      {hasMessages && <p>You have {count} new messages!</p>}
    </div>
  );
}```

Real-world Example:

  return (
    <div>
      {user ? (
        <div>
          <h2>Hello, {user.name}!</h2>
          <button>Logout</button>
        </div>
      ) : (
        <div>
          <h2>Welcome, Guest!</h2>
          <button>Login</button>
        </div>
      )}
    </div>
  );
}```

Rendering Arrays

Rendering arrays lets you display lists of data dynamically. Use the `.map()` method to transform array data into JSX elements!

Basic Example:

  const todos = ['Buy groceries', 'Walk the dog', 'Learn React'];
  
  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>{todo}</li>
      ))}
    </ul>
  );
}```

Advanced Example with Objects:

  const products = [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Mouse', price: 29 },
    { id: 3, name: 'Keyboard', price: 79 }
  ];
  
  return (
    <div>
      {products.map((product) => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>Price: ${product.price}</p>
        </div>
      ))}
    </div>
  );
}```

Key Points:

- Always use a unique `key` prop when rendering lists

- Keys help React identify which items have changed, added, or removed

- Use unique IDs as keys (not array index if the list can change)

- The `key` prop is not accessible inside the component

Lifting State Up

When two or more components need to share the same state, you "lift" the state to their closest common parent component. The parent then passes the state down as props.

Problem: Two sibling components need to share data

Solution: Move the state to their parent!

  const [username, setUsername] = useState('');
  
  return (
    <div>
      <InputComponent username={username} setUsername={setUsername} />
      <DisplayComponent username={username} />
    </div>
  );
}

function InputComponent({ username, setUsername }) {
  return (
    <input 
      value={username}
      onChange={(e) => setUsername(e.target.value)}
      placeholder="Enter username"
    />
  );
}

function DisplayComponent({ username }) {
  return (
    <div>
      <h2>Hello, {username || 'Guest'}!</h2>
    </div>
  );
}```

useRef Hook

useRef is like a box that holds a value that persists across renders but doesn't cause re-renders when changed. Perfect for accessing DOM elements directly!

Use Case 1: Accessing DOM Elements


function FocusInput() {
  const inputRef = useRef(null);
  
  const handleFocus = () => {
    inputRef.current.focus();
  };
  
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
}```

Use Case 2: Storing Mutable Values

  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);
  
  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  };
  
  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}```

useRef vs useState:

- useState: Causes re-render when value changes

- useRef: Does NOT cause re-render when value changes

- Access useRef value with `.current`

useEffect Hook

useEffect lets you perform side effects in your components. Side effects are operations that interact with the outside world: fetching data, updating the DOM, setting up subscriptions, timers, etc.

Basic Syntax:


useEffect(() => {
  // Side effect code here
  
  return () => {
    // Cleanup code (optional)
  };
}, [dependencies]);```

Example 1: Run on Every Render

  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('Component rendered!');
  }); // No dependency array
  
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}```

Example 2: Run Once on Mount (Empty dependency array)

  const [user, setUser] = useState(null);
  
  useEffect(() => { 
    // Fetch user data when component mounts
    fetch('https://api.example.com/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []); // Empty array = run once
  
  return user ? <h1>{user.name}</h1> : <p>Loading...</p>;
}```

Example 3: Run When Specific Value Changes

  const [results, setResults] = useState([]);
  
  useEffect(() => {
    if (searchQuery) {
      fetch(`https://api.example.com/search?q=${searchQuery}`)
        .then(res => res.json())
        .then(data => setResults(data));
    }
  }, [searchQuery]); // Runs when searchQuery changes
  
  return (
    <div>
      {results.map(item => <p key={item.id}>{item.name}</p>)}
    </div>
  );
}```

Example 4: Cleanup Function

  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
    
    // Cleanup: runs when component unmounts
    return () => clearInterval(interval);
  }, []);
  
  return <p>Seconds: {seconds}</p>;
}```

Common Use Cases:

- Fetching data from APIs

- Setting up subscriptions or event listeners

- Updating document title

- Setting up timers

- Manually changing the DOM

Key Points:

- useEffect runs AFTER the component renders

- Empty dependency array `[]` = runs once on mount

- No dependency array = runs on every render

- With dependencies `[value]` = runs when value changes

- Return a cleanup function to prevent memory leaks

- Always include all dependencies that your effect uses