Props As The Data Backbone

React’s component-driven architecture relies on a predictable flow of data. Props (short for properties) are the primary mechanism for moving that data between components, typically from a parent down to its children. They can carry any JavaScript value — numbers, strings, arrays, functions, or objects — and are passed to a component much like attributes are set on an HTML element:

<PostList posts={postsList} />

This example passes a single prop, posts, holding the value {postsList}, to the PostList component. Because data flows one way, parent-to-child, components stay decoupled and easier to reason about. A callback function passed as a prop is the standard way to let a child communicate an event back up to its parent.

Accessing Props In Class And Function Components

Consider a simple app that renders a list of user posts. The data structure pairs each post’s content with the author’s name:

const postsList = [
  {
    id: 1,
    content: "The world will be out of the pandemic soon",
    user: "Lola Lilly",
  },
  {
    id: 2,
    content: "I'm really exited I'm getting married soon",
    user: "Rebecca Smith",
  },
  {
    id: 3,
    content: "What is your take on this pandemic",
    user: "John Doe",
  },
  {
    id: 4,
    content: "Is the world really coming to an end",
    user: "David Mark",
  },
];

The top-level App component holds this data and passes it down to a PostList component:

const App = () => {
  return (
    <div>
      <PostList posts={postsList} />
    </div>
  );
};

In a class component like PostList, props are accessible via the this.props object. Here, the component maps over the incoming posts array and renders each item as a separate Post component:

class PostList extends React.Component {
  render() {
    return (
      <React.Fragment>
        <h1>Latest Users Posts</h1>
        <ul>
          {this.props.posts.map((post) => {
            return (
              <li key={post.id}>
                <Post {...post} />
              </li>
            );
          })}
        </ul>
      </React.Fragment>
    );
  }
}

Note the key prop assigned to each rendered item. React uses this unique identifier (the post’s id in this case) to efficiently track changes to list items. The remaining post data is spread onto the Post component with {...post}.

The Post component itself can be written as a functional component, where props arrive as the function’s first argument:

const Post = (props) => {
  return (
    <div>
      <h2>{props.content}</h2>
      <h4>username: {props.user}</h4>
    </div>
  );
};

Passing Functions To Change State

Props are immutable — a child component should never try to modify its own props. Instead, to change state based on something that happens in a child, the parent passes a function down as a prop. The child can then invoke that function, and the parent’s state update propagates back down through the component tree.

A minimal example has a parent App component holding a boolean state value and a function to toggle it:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isShow: true,
    };
  }
  toggleShow = () => {
    this.setState((state) => ({ isShow: !state.isShow }));
  };
  render() {
    return (
      <div>
        <ChildComponent isShow={this.state.isShow} clickMe={this.toggleShow} />
      </div>
    );
  }
}

The state value, isShow, and the toggleShow() function are both passed to ChildComponent as the props isShow and clickMe. The child doesn’t need to know how the function works — it just calls the prop when the user interacts:

class ChildComponent extends React.Component {
  clickMe = () => {
    this.props.clickMe();
  };
  render() {
    const greeting = "Welcome to React Props";
    return (
      <div style={{ textAlign: "center", marginTop: "8rem" }}>
        {this.props.isShow ? (
          <h1 style={{ color: "green", fontSize: "4rem" }}>{greeting}</h1>
        ) : null}
        <button onClick={this.clickMe}>
          <h3>click Me</h3>
        </button>
      </div>
    );
  }
}

When the button in the child is clicked, it triggers the parent’s state change. The updated state is passed back down as a prop, and all affected components re-render. This pattern keeps the parent as the single source of truth while still allowing the child to influence that truth.

Type Checking With PropTypes

As a React app grows, it becomes increasingly valuable to know that the data arriving in a component is the right shape. PropTypes provide runtime validation to ensure components receive the correct data types. It is an entirely optional practice, but one that catches bugs early and makes component interfaces more explicit.

To use PropTypes, the package must first be installed. With npm:

npm install --save prop-types

Or with Yarn:

yarn add prop-types

Then, import it into the component file:

import PropTypes from 'prop-types';

For the Post component from earlier, PropTypes can declare the expected types of its props:

Post.proptypes = {
  id: PropTypes.number,
  content: PropTypes.string,
  user: PropTypes.string
}

Validators like PropTypes.string and PropTypes.number check the id, content, and user props against their declared types. This makes the contract between components clear and surfaces mismatches during development.

Validation can be made mandatory. Appending isRequired to a validator causes React to warn if that prop isn’t provided:

Post.proptypes = {
  id: PropTypes.number.isRequired,
  content: PropTypes.string.isRequired,
  user: PropTypes.string.isRequired
}

The PropTypes library includes a broad set of validators for common types:

Component.proptypes = {
  stringProp: PropTypes.string,         // The prop should be a string
  numberProp: PropTypes.number,         // The prop should be a number
  anyProp: PropTypes.any,               // The prop can be of any data type
  booleanProp: PropTypes.bool,          // The prop should be a function
  functionProp: PropTypes.func          // The prop should be a function
  arrayProp: PropTypes.array            // The prop should be an array
}

The full list is available in the React documentation.

Providing Defaults For Optional Props

For optional props that aren’t marked with isRequired, it’s good practice to define a defaultProps value. This ensures the prop always has a fallback if the parent doesn’t supply one, preventing undefined values from reaching the component logic:

Class Profile extends React.Component{

  // Specifies the default values for props
  static defaultProps = {
    name: 'Stranger'
  };

  // Renders "Welcome, Stranger":
  render() {
    return <h2> Welcome, {this.props.name}<h2>
  }
}

In this Profile class component, if a name prop isn’t passed from the parent, the prop falls back to the string Stranger. Defining defaultProps for every optional prop is a simple habit that improves component robustness.

Mastering props means understanding both the one-way flow of data that makes React predictable, and the validation layer of PropTypes that makes that data trustworthy. Together, they form a core part of building interfaces that scale cleanly.