Components And Props

A component is just a function you can call with different arguments -- once that clicks, the entire component model falls into place.

May 15, 20264 min read2 / 2

In the previous post we rendered a single hard-coded heading. That is not how anything actually gets built. A real UI has repeated structure -- a menu, a list, a grid -- where the shape is the same but the data differs.

That is what components are for.

Turning a Function Into a Template

Here is the key insight: a React component is just a function. Its return value is a description of what to render. Its parameters -- called props -- are how you make that description flexible.

Start with the simplest possible case, a component that is entirely static:

JavaScript
const Pizza = () => { return React.createElement( "div", {}, React.createElement("h2", {}, "Pepperoni Pizza"), React.createElement("p", {}, "Tomato sauce, mozzarella, pepperoni") ); };

This works, but only for one pizza. If the menu has five items, you are copying and pasting five times, and every change to the layout requires five edits.

The fix is to accept data as an argument:

JavaScript
const Pizza = (props) => { return React.createElement( "div", {}, React.createElement("h2", {}, props.name), React.createElement("p", {}, props.description) ); };

Now Pizza is a template. Call it with different data and it produces different output. This is what props are -- arguments to a component function. Nothing more exotic than that.

Rendering Multiple Instances

With a parameterized component, rendering the full menu is just a matter of calling it with different data:

JavaScript
const App = () => { return React.createElement( "div", {}, React.createElement(Pizza, { name: "Pepperoni", description: "Tomato sauce, mozzarella, pepperoni", }), React.createElement(Pizza, { name: "Margherita", description: "Tomato sauce, fresh mozzarella, basil", }), React.createElement(Pizza, { name: "Hawaiian", description: "Tomato sauce, mozzarella, ham, pineapple", }) ); };

Each React.createElement(Pizza, {...}) call produces a separate instance of the component with its own data. The component definition is written once. The instances are many.

A Pizza component accepts props and renders multiple instances with different data ExpandA Pizza component accepts props and renders multiple instances with different data

Returning Multiple Siblings

Notice that the Pizza component returns a div wrapping two elements. What if you want two siblings at the top level without a wrapper element?

You can return an array:

JavaScript
const Pizza = (props) => { return [ React.createElement("h2", {}, props.name), React.createElement("p", {}, props.description), ]; };

Both forms are valid. The array approach avoids adding an unnecessary DOM node. Once JSX enters the picture, there is a cleaner syntax for this (called a Fragment), but the underlying mechanic is the same -- React accepts an array of elements as the return value of a component.

Why Components Work This Way

React came out of a period when the dominant pattern for frontend code was separating concerns into models, views, and view-models -- three layers that had to be wired together. The idea made sense on paper. In practice, a change to one feature touched files in all three layers. The cognitive load of tracing one user-facing change through three abstraction layers was significant.

React's answer was to stop separating by abstraction layer and start separating by feature. Everything related to displaying a pizza -- the structure, the data, eventually the styles and the behaviour -- lives in one component. If the pizza display changes, you change one file.

This is why components encapsulate everything about one concern rather than splitting it across a view, a model, and a controller. The trade-off is that a component file can grow. The benefit is that it is immediately obvious where to look when something breaks.

Before the Tools Arrive

At this point the entire codebase is one HTML file and one JS file. There are no npm packages, no bundler, no config. Before the next post adds Prettier, ESLint, and eventually Vite, it is worth sitting with how simple the baseline actually is.

Everything added from here is tooling that reduces friction at scale. None of it changes what a component is: a function that accepts data and returns a description of UI.

In the next post, both Prettier and ESLint get wired in -- and the reason to keep them separate becomes clear immediately.

The Essentials

  1. Props are arguments. A component is a function. Props are its parameters. Pass different props, get different output from the same component definition.
  2. One definition, many instances. Define a component once. Call it with different data to produce different rendered instances. This is the composability model.
  3. Arrays as return values. A component can return an array of elements to render multiple siblings without a wrapper element.
  4. Encapsulation by feature. React groups everything related to one UI concern into one component rather than splitting it across model/view/controller layers.
  5. Predict before running. If you render five pizza instances, expect five h2 elements and five p elements. Counting expected elements before looking at the browser sharpens the mental model.

Further Reading and Watching

Practice what you just read.

Build a Pizza Card
1 exercise