React Components, State & Props: A Beginner's Guide

Blog / ReactJS · May 6, 2025 · Updated June 10, 2026 · 8 min read
React Components, State & Props: A Beginner's Guide

Components, props, and state are the three building blocks of every React app. In plain terms: a component is a JavaScript function that returns UI (markup written in JSX); props are read-only inputs a parent passes down to a child; and state is internal data a component owns that can change over time and triggers a re-render when it does. Get these three ideas straight and the rest of React becomes much easier to reason about.

This guide uses function components and hooks, which are the default way to write React in 2026 (React 18 and 19). Class components still work and you will see them in older codebases, but new code is almost always function-based.

Function components and JSX

A React component is just a function whose name starts with a capital letter and that returns some UI. The markup-looking syntax inside the function is JSX — a JavaScript extension that compiles down to plain function calls. You use components by writing them like HTML tags, for example <Welcome />.

Here is the smallest useful component. It returns a heading and is then placed into the page:

import { createRoot } from 'react-dom/client';

function HelloWorld() {
  return <h1>Hello, world!</h1>;
}

// React 18+ mounts the app with createRoot
const root = createRoot(document.getElementById('root'));
root.render(<HelloWorld />);

A few things to know about JSX:

  • Use className instead of class, because class is a reserved word in JavaScript.
  • Embed any JavaScript expression with curly braces, e.g. <p>{2 + 2}</p>.
  • A component must return a single root element. Wrap siblings in a <div> or in a fragment (<>...</>).

Most real apps are created with a tool like Vite rather than loading React from a script tag, but the component concept is identical. If you are setting up from scratch, see our walkthrough on setting up a React environment and your first app.

Props: read-only inputs from a parent

Props (short for “properties”) are how a parent component passes data down to a child. They behave like arguments to a function: the child receives them and uses them to render. The golden rule is that props are read-only — a component must never modify the props it receives. This keeps data flow predictable.

You typically destructure props in the function signature so the code reads cleanly. You can also give a prop a default value:

// Destructure props directly, with a default value for `name`
function Greeting({ name = 'friend' }) {
  return <p>Hello, {name}!</p>;
}

function App() {
  return (
    <div>
      <Greeting name="Ada" />   {/* renders: Hello, Ada! */}
      <Greeting />              {/* renders: Hello, friend! */}
    </div>
  );
}

The special children prop

Anything you nest between a component's opening and closing tags is passed automatically as a prop called children. This is how wrapper and layout components work:

function Card({ children }) {
  return <div className="card">{children}</div>;
}

function App() {
  return (
    <Card>
      <h2>Title</h2>
      <p>Anything in here arrives as the `children` prop.</p>
    </Card>
  );
}

State with useState

State is data a component owns and can change while the app is running — a counter value, the text in an input, whether a menu is open. In function components you declare state with the useState hook. It returns a pair: the current value and a setter function to update it. Calling the setter tells React to re-render the component with the new value.

const [count, setCount] = useState(0);

Here 0 is the initial value, count is the current value, and setCount is how you change it.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1); // schedules a re-render with the new value
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

Why you don't mutate state directly

Never change state in place — always go through the setter. Writing count = count + 1 or pushing into a state array with array.push(...) will not trigger a re-render, and React will not see the change. Instead, create a new value and pass it to the setter:

  • Numbers/strings: setCount(count + 1)
  • Objects: setUser({ ...user, name: 'New' })
  • Arrays: setItems([...items, newItem])

When the next value depends on the previous one (for example, multiple updates in a row), use the updater function form so you always work from the latest state:

// Safer when updates may batch together
setCount(prevCount => prevCount + 1);

// Updating an object immutably
setUser(prevUser => ({ ...prevUser, name: 'Grace' }));

Props vs state: the key differences

Beginners mix these up constantly, so keep this comparison handy. Props come in from a parent and are read-only; state is owned by the component and is mutable through its setter.

Aspect Props State
Who sets it The parent component passes it in The component itself, with useState
Can it change? Read-only inside the receiving component Yes, via the setter (e.g. setCount)
Direction of flow Passed down (parent → child) Local to the component
Causes a re-render? When the parent passes new props When you call the setter
Typical use Configure a component, share data downward Track values that change over time
Analogy Function arguments Local variables that persist across renders

Putting it together: props + state + an event handler

Most real components combine all three ideas. Below, a Parent owns the state and passes both a value (count, a prop) and a callback (onIncrement, also a prop) down to a presentational Child. The child stays simple and reusable — it just displays what it is given and calls back up when the button is clicked.

import { useState } from 'react';

// Child: receives data and a callback as props (read-only)
function CounterDisplay({ count, onIncrement }) {
  return (
    <div>
      <p>Current count: {count}</p>
      <button onClick={onIncrement}>Add one</button>
    </div>
  );
}

// Parent: owns the state, passes value + handler down as props
function Parent() {
  const [count, setCount] = useState(0);

  return (
    <CounterDisplay
      count={count}
      onIncrement={() => setCount(c => c + 1)}
    />
  );
}

One-way data flow

React uses one-way (top-down) data flow: state lives in a parent and travels down to children as props. Children never reach up and change a parent's state directly. Instead, the parent passes a function (like onIncrement above) and the child calls it, letting the parent decide how to update. This keeps the data's source of truth in one place and makes bugs easier to trace — you always know who owns each piece of data.

Lifting state up

When two sibling components need to share the same data, you lift state up to their closest common parent. The parent holds the state and passes it down to both children as props, along with any setters they need. This is the standard React pattern for keeping components in sync without duplicating data.

For example, if a temperature input and a chart both need the current temperature, you store temperature in their shared parent rather than in either child.

Controlled inputs

Form fields work best as controlled inputs: the input's value is driven by state, and an onChange handler updates that state on every keystroke. This makes React the single source of truth for the field, so you can validate, format, or reset it easily.

import { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  return (
    <form>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Your name"
      />
      <p>Hello, {name || 'stranger'}!</p>
    </form>
  );
}

A note on class components (legacy)

You may still encounter class components that extend React.Component, define state in a constructor, and use this.setState plus a render() method. They are fully supported and you will meet them when maintaining older apps. For new code in 2026, however, function components with hooks are the default — they are shorter, easier to test, and let you reuse logic with custom hooks. When you are ready, learn how components mount, update, and unmount in our guide to React component lifecycle methods, which maps the old lifecycle to the useEffect hook.

If your team is building or modernising a React product, MicroPyramid's React development services cover everything from greenfield apps to migrating legacy class-based code to modern hooks. Curious how React fits against cross-platform options? See our comparison of React Native vs Flutter.

Frequently Asked Questions

What's the difference between state and props?

Props are read-only inputs passed into a component by its parent; the component cannot change them. State is data the component owns and can change over time through its setter (such as setCount). Both trigger a re-render when they change — props when the parent sends new ones, state when you call its setter.

Can props change?

Not from inside the component that receives them — props are read-only there. They only “change” when the parent re-renders and passes new values down. If a component needs to change a value itself, that value should be state, not a prop.

What is useState in React?

useState is the hook that adds state to a function component. You call it with an initial value and it returns the current value plus a setter, e.g. const [count, setCount] = useState(0). Calling the setter updates the value and re-renders the component.

Why shouldn't I mutate state directly?

React detects changes by comparing values, so editing state in place (like state.push(...) or reassigning a variable) won't trigger a re-render and the UI gets out of sync. Always create a new value — spread objects/arrays into new ones — and pass it to the setter.

What does “lifting state up” mean?

It means moving shared state to the closest common parent of the components that need it, then passing it down as props. This keeps a single source of truth so sibling components stay in sync.

Should I use function components or class components in 2026?

Use function components with hooks for new code — they are the modern default in React 18 and 19, are more concise, and make logic reuse easier. Class components are still supported, so keep using them when maintaining existing class-based codebases.

Share this article