Day 3: Understanding State and Props in React

Welcome to Day 3 of our React teaching series! In the previous two articles, we learned how to set up our development environment and get started with React components and JSX. Today, we will dive deeper into two essential concepts in React: state and props. Understanding how to manage state and pass data through props is fundamental to building dynamic and interactive user interfaces. So, let’s begin!

1. Understanding State

State is an integral part of React that allows components to store and manage data internally. It enables us to create components that respond to user interactions and external events. In this section, we will cover the following topics:

What is State and Why is it Important?

State represents the internal data of a component that can change over time. It plays a vital role in building dynamic UIs, as it allows us to update and reflect changes in the component’s data. By modifying the state, we can trigger re-rendering of the component, ensuring that the UI reflects the latest data.

Declaring State with the useState Hook

In functional components, we use the useState hook to declare and manage state. This hook provides a way to add stateful behavior to functional components without the need for class components. Let’s see an example:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

In this example, we declare a state variable called count using the useState hook. The initial value of count is set to 0. We can update the count value by calling the setCount function, which is provided by the useState hook.

Modifying State with setState

To modify the state in React, we use the setState function. It allows us to update the state with new values. We can either pass a new value directly to setState or provide a function that receives the previous state and returns the new state. Let’s modify our previous example to showcase the use of setState:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

In this modified example, we extract the increment logic into a separate function called increment. When the button is clicked, the increment function is called, and the setCount function is used to update the count state.

Rendering State in JSX

To render the state within JSX, we can include the state variable within curly braces {}. React will replace the curly braces with the current value of the state variable. Let’s modify our example to display a message based on the count:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <p>Count: {count}</p>
      {count > 0 && <p>Great! The count is greater than zero.</p>}
      <button onClick={increment}>Increment</button>
    </div>
  );
}

In this example, we conditionally render a message “Great! The count is greater than zero” if the count is greater than zero.

The Importance of Immutability When Working with State

In React, it is crucial to maintain immutability when updating the state. We should never directly modify the state variable; instead, we should create a new copy of the state and modify that. This is because React uses shallow comparison to determine if the state has changed and whether a re-render is necessary. If we directly modify the state variable, React may not detect the change and fail to update the UI. Let’s see an example:

import React, { useState } from 'react';

function App() {
  const [person, setPerson] = useState({ name: 'Ali', age: 25 });
  const increaseAge = () => {
    // Incorrect way: modifying the state directly
    person.age++;
    setPerson(person);
  };
  return (
    <div>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <button onClick={increaseAge}>Increase Age</button>
    </div>
  );
}

In this example, we directly modify the person object's age property, which violates the immutability principle. To fix this, we need to create a new copy of the person object and modify that instead:

import React, { useState } from 'react';

function App() {
  const [person, setPerson] = useState({ name: 'Ali', age: 25 });
  const increaseAge = () => {
    // Correct way: creating a new copy of the state and modifying that
    const updatedPerson = { ...person, age: person.age + 1 };
    setPerson(updatedPerson);
  };
  return (
    <div>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <button onClick={increaseAge}>Increase Age</button>
    </div>
  );
}

By creating a new copy of the person object and modifying that, we ensure immutability and allow React to detect the state change correctly.

2. Working with Props

Props (short for properties) are a way to pass data from a parent component to its child components. Understanding props is essential for building reusable and modular components. In this section, we will explore the following concepts:

What are Props and How Do They Work?

Props are read-only properties that are passed to a component by its parent component. They allow data to flow down the component tree, enabling communication between components. Props are immutable, meaning they cannot be modified by the component receiving them.

Passing Props from a Parent Component to a Child Component

To pass props from a parent component to a child component, we simply add attributes to the child component element within the parent component’s JSX. Let’s see an example:

import React from 'react';

function ParentComponent() {
  const name = 'Alice';
  return <ChildComponent name={name} />;
}
function ChildComponent(props) {
  return <p>Hello, {props.name}!</p>;
}

In this example, we pass the name prop from the ParentComponent to the ChildComponent by including the name={name} attribute within the JSX of the ParentComponent. The ChildComponent receives the prop as an argument and renders it within a paragraph element.

Accessing Props Within a Child Component

To access props within a child component, we can simply reference them using the props object. Let's modify our previous example to showcase accessing props:

import React from 'react';

function ParentComponent() {
  const name = 'Alice';
  return <ChildComponent name={name} />;
}

function ChildComponent(props) {
  return (
    <div>
      <p>Hello, {props.name}!</p>
      <p>Length of name: {props.name.length}</p>
    </div>
  );
}

In this modified example, the ChildComponent renders the value of the name prop within a paragraph element. Additionally, it displays the length of the name prop.

Using Default Props and Prop Types for Validation

In React, we can define default values for props and specify their expected types using prop types. This helps document and validate the props passed to a component. Let’s see an example:

import React from 'react';
import PropTypes from 'prop-types';

function PersonDetails(props) {
  return (
    <div>
      <p>Name: {props.name}</p>
      <p>Age: {props.age}</p>
    </div>
  );
}
PersonDetails.defaultProps = {
  age: 25,
};
PersonDetails.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
};

In this example, we set a default value of 25 for the age prop using defaultProps. We also define the expected prop types using propTypes. The name prop is marked as isRequired, indicating that it must be provided, while the age prop is optional and expected to be a number.

3. State vs. Props

In this section, we will compare and contrast state and props to understand their respective use cases. Let’s discuss the key differences between state and props, when to use each, and best practices for managing them in React applications.

Key Differences Between State and Props

  • State is managed internally by a component, whereas props are passed from parent to child components.

  • State can change over time and triggers re-rendering, while props are immutable and cannot be modified by the receiving component.

  • State is specific to a single component, whereas props can flow down the component tree, enabling communication between components.

When to Use State and When to Use Props

  • Use state when you need to manage and update internal data within a component.

  • Use props to pass data and communicate between components, especially from parent to child components.

  • If a value needs to be shared and modified across multiple components, it is recommended to lift the state up to a common parent component.

Best Practices for Managing State and Props

  • Minimize the usage of state by moving it higher up in the component tree whenever possible.

  • Keep the state as minimal as necessary and derive computed values from it.

  • Use props for data that doesn’t change within the component and is provided by a parent component.

  • Follow the principle of immutability when updating state to ensure proper re-rendering and component behavior.

4. Hands-On Exercise: Creating a Counter Component

To solidify your understanding of state and props, let’s work on a hands-on exercise. We will create a simple React application that demonstrates how to use state and props effectively.

Exercise: Creating a Counter Component

import React, { useState } from 'react';

function Counter(props) {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount(count + 1);
  };
  const decrement = () => {
    setCount(count - 1);
  };
  return (
    <div>
      <p>Count: {count}</p>
      <p>Message: {props.message}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}
export default Counter;

In this example, we have the Counter component that receives a prop called message from its parent component. The count state is managed using the useState hook, allowing us to keep track of the current count. The increment function increments the count state when the increment button is clicked, and the decrement function decrements the count state when the decrement button is clicked.

The count state is rendered in a paragraph element as “Count: {count}”, and the value of the message prop is displayed in another paragraph element as "Message: {props.message}".

Conclusion

Congratulations on making significant progress in your React journey! In this article, we covered the essential concepts of state and props in React. Understanding how to manage state and pass data through props is crucial for building dynamic and interactive user interfaces. Remember to practice what you’ve learned by building your own projects and experimenting with different state and prop configurations.

Remember, the key to mastering React is consistent practice and experimentation. Happy coding!