React is an open source JavaScript library for building user interfaces from reusable components. A component can combine rendering logic, state and event handling in one focused unit, which makes component boundaries a useful way to organize large applications.
React applications commonly use JSX or TSX. JSX looks like HTML, but it is JavaScript syntax that lets a component describe the user interface it should render. Modern React code often uses function components and Hooks rather than class components.
The following example defines a small function component. It calculates a random number and includes that value in the JSX returned by the component:
import React from 'react';
const Header = () => {
const randomNumber = Math.floor(Math.random() * 100);
return (
<div className="App-header">
Welcome! You've been assigned the name number: {randomNumber}.
</div>
);
};
export default Header;Developers with an object-oriented background can use familiar OOP ideas to reason about React, but the analogy has limits. A modern React function component is a JavaScript function, not a class, and React does not require class inheritance to reuse or vary behavior.
Still, three OOP ideas map usefully to React development: encapsulation through component boundaries, polymorphic behavior through props and children, and composition by assembling smaller components into larger ones. The code for this entire demonstration project is on GitHub.
Encapsulation with React components
Encapsulation groups related state and behavior behind a clear boundary. In class-based OOP that boundary is often a class. In React, a component provides a similar organizational boundary even though a function component is not itself a class.
The following Ad component keeps its identifier, message state, default content and click-handling logic together. Consumers of the component do not need to know how those details are implemented. They interact with the component primarily through its props and children.
import { useId, useState } from 'react';
const Ad = ({ children, onClick }) => {
const id = useId();
const [message, setMessage] = useState('');
const [showOopDemoMessage, setShowOopDemoMessage] = useState(false);
const defaultAd = `Default advertisement for the Ad component [${id}].`;
const showMessage = () => {
setMessage('Go to our website and buy our stuff!');
setShowOopDemoMessage(true);
};
const handleClick = () => {
if (onClick) {
onClick();
return;
}
showMessage();
};
return (
<div className="oppdemo-box">
<div id={id}>
<div className="oppdemo-font">
{children ? children : defaultAd}
</div>
<button className="oppdemo-button" onClick={handleClick}>
Click me
</button>
</div>
<div className={showOopDemoMessage ? 'oppdemo-message' : ''}>
{message}
</div>
</div>
);
};
export default Ad;Figure 1 below shows the result of rendering the Ad component with its default data and behavior on a webpage.
The key point is that the Ad component owns its implementation details. State created with useState() and local helper functions stay inside the component, while callers work with the public API the component exposes through props and children.
React also offers other ways to share data, such as context and external state stores, so props are not the only possible input. But props and children are the simplest way to make a reusable component configurable.
The following App component renders the Ad with its default behavior and then renders additional instances with custom children and click handlers:
import './App.css';
import Ad from './components/Ad';
import Header from './components/Header';
const App = () => {
return (
<div className="App">
<Header />
<Ad />
<Ad onClick={() => prompt('Is our stuff really the best?')}>
Buy my stuff now because it's the best!
</Ad>
<Ad
onClick={() =>
alert('Take advantage of overnight shipping and buy more stuff now!')
}
>
We offer overnight shipping!
</Ad>
</div>
);
};
export default App;The second Ad instance supplies both custom child content and a custom click handler:
<Ad onClick={() => prompt('Is our stuff really the best?')}>
Buy my stuff now because it's the best!
</Ad>Putting the text Buy my stuff now because it's the best! between the opening <Ad> and closing </Ad> tags supplies the component's children. The component renders that content instead of its default advertisement, as shown in Figure 2:
The component now has the same identity and implementation but produces a different result from different input. That is useful to think of as polymorphic behavior, although it is not class-based polymorphism in the traditional inheritance sense.
Polymorphic behavior with props and children
In traditional OOP, polymorphism often means that code can work through a common interface while different concrete types provide different behavior. React usually achieves variation in a different way: the same component receives different props, child content or callback functions and adapts what it renders or does.
This distinction matters in JavaScript because JavaScript does not provide traditional method overloading based solely on parameter signatures. In React, a more practical comparison is a component that keeps the same public shape but changes its output or behavior according to the props it receives.
The following version of Ad shows that idea directly. The expression {children ? children : defaultAd} chooses between caller-supplied children and the component's default content.
import { useId, useState } from 'react';
const Ad = ({ children, onClick }) => {
const id = useId();
const [message, setMessage] = useState('');
const [showOopDemoMessage, setShowOopDemoMessage] = useState(false);
const defaultAd = `Default advertisement for the Ad component [${id}].`;
const showMessage = () => {
setMessage('Go to our website and buy our stuff!');
setShowOopDemoMessage(true);
};
const handleClick = () => {
if (onClick) {
onClick();
return;
}
showMessage();
};
return (
<div className="oppdemo-box">
<div id={id}>
<div className="oppdemo-font">
{children ? children : defaultAd}
</div>
<button className="oppdemo-button" onClick={handleClick}>
Click me
</button>
</div>
<div className={showOopDemoMessage ? 'oppdemo-message' : ''}>
{message}
</div>
</div>
);
};
export default Ad;The children prop contains the content placed between a component's opening and closing tags. Recall this JSX from above:
<Ad>Buy my stuff now because it's the best!</Ad>
The string is passed to Ad through children. The expression {children ? children : defaultAd} tells the component to render caller-supplied children when they exist and otherwise fall back to its default content.
Varying behavior with function props
React can vary behavior as well as rendered content. A parent component can pass a callback function through a prop, and the child can call that function in response to an event.
The handleClick function makes that choice explicit:
const handleClick = () => {
if (onClick) {
onClick();
} else {
showMessage();
}
};If the parent supplies an onClick prop, handleClick calls it. Otherwise, the component falls back to its local showMessage() behavior.
When Ad is rendered as <Ad /> with no onClick prop, clicking the button therefore runs showMessage(), as shown in Figure 3.
The parent can supply a different behavior by passing an onClick callback. In this example, clicking the button invokes JavaScript's prompt() function instead of the component's default showMessage() behavior:
<Ad onClick={() => prompt('Is our stuff really the best?')}>
Buy my stuff now because it's the best!
</Ad>Thus, when the user clicks the Click Me button, the prompt() function is called instead of the default showMessage() function declared internally in the Ad component. Figure 4 shows the result when a user clicks the Click Me button with the overridden click behavior.
For an OOP developer, this resembles polymorphic dispatch because the caller can substitute behavior behind a stable interface. The mechanism is different, however: React is passing a function value through a prop rather than overriding a method through class inheritance.
Using composition in React and JavaScript
Composition builds larger units from smaller ones. It is especially important in React because component reuse is normally achieved by nesting and combining components rather than by creating deep inheritance hierarchies.
The code below describes a React component named DateReporter that reports the current date.
const DateReporter = () => {
const currentDate = new Date().toLocaleDateString();
return (
<div className="datereporter-box">
<div>The current date is:</div>
<div>{currentDate}</div>
</div>
);
};
export default DateReporter;The following version of Ad composes the DateReporter component directly into its rendered output:
import { useId, useState } from 'react';
import DateReporter from './DateReporter';
const Ad = ({ children, onClick }) => {
const id = useId();
const [message, setMessage] = useState('');
const [showOopDemoMessage, setShowOopDemoMessage] = useState(false);
const defaultAd = `Default advertisement for the Ad component [${id}].`;
const showMessage = () => {
setMessage('Go to our website and buy our stuff!');
setShowOopDemoMessage(true);
};
const handleClick = () => {
if (onClick) {
onClick();
return;
}
showMessage();
};
return (
<div className="oppdemo-box">
<div id={id}>
<div className="oppdemo-font">
{children ? children : defaultAd}
</div>
<button className="oppdemo-button" onClick={handleClick}>
Click me
</button>
</div>
<div className={showOopDemoMessage ? 'oppdemo-message' : ''}>
{message}
</div>
<DateReporter />
</div>
);
};
export default Ad;The result of adding the <DateReporter /> component to the <Ad /> component is shown below in Figure 5.
Composition keeps responsibilities separated. A parent can add capability by rendering another component, while the child component can evolve independently as long as its public contract remains stable.
This makes composition one of the most important techniques for building reusable, maintainable React applications.
Putting it all together
React has its own programming model, and it should not be forced into a class-based OOP mold. Modern function components, Hooks and props work differently from inheritance, virtual methods and method overloading.
Still, OOP experience is useful. Component boundaries can help developers reason about encapsulation, props and callbacks can produce polymorphic behavior, and composition provides a powerful way to assemble reusable features. Understanding both the similarities and the differences makes React easier to learn without creating misleading mental models.
Bob Reselman is a software developer, system architect and writer. His expertise ranges from software development technologies to techniques and culture.