What Are React Components and How Do You Use Them?
A React component is a reusable piece of UI defined as a JavaScript function (or, historically, a class) that returns markup. You build an interface by composing many small components, passing data in through props and tracking change with state. If you're starting a project or adding to one, you'll usually write your own components for app-specific logic and pull in prebuilt ones from a library or registry for common UI like buttons, heroes, and cards. The sections below cover how components work, when to use which style, and how to add one and confirm it renders.
The core idea: a function that returns UI
A component takes an input object called props and returns what should appear on screen. State is data a component owns and can change over time; when it changes, React re-renders that component.
function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
// Used like an HTML tag:
<Greeting name="Ada" />
Two rules matter in practice:
- Props flow down, events flow up. A parent passes data to a child via props; the child signals change by calling a function the parent handed it.
- Components must be pure with respect to their inputs. Given the same props and state, they should render the same output. Side effects (fetching, subscriptions) belong in hooks like
useEffect.
Function components vs. class components
Modern React uses function components with hooks. Class components still appear in older codebases and some libraries, so you should recognize them even if you don't write them.
| Dimension | Function component + hooks | Class component |
|---|---|---|
| Syntax | Plain function returning JSX | class extends React.Component with a render() method |
| State | useState, useReducer |
this.state and this.setState |
| Side effects | useEffect |
Lifecycle methods (componentDidMount, etc.) |
this binding |
Not applicable | Requires binding or arrow methods |
| Current default | Yes | Legacy, still supported |
If you're starting fresh, use function components. Reach for a class only when maintaining existing code or integrating a library that requires it.
Composition: build big UI from small pieces
The main way you scale a React app is by splitting UI into small components and combining them. Three patterns cover most cases:
- Passing
childrenlets a wrapper render whatever you nest inside it:function Card({ children }) { return <div className="card">{children}</div>; } <Card><Greeting name="Ada" /></Card> - Lifting state up means moving shared state to the closest common parent so two siblings can stay in sync, rather than duplicating it in each.
- Keeping components focused — one component, one job — makes them easier to reuse and test.
Where prebuilt components come from
You rarely build everything yourself. Two distribution models dominate:
- Package libraries ship compiled code you import from
node_modules. You get updates by bumping a version, but you don't own or edit the source. - Registries and copy-in source give you the actual component file, which lands in your repo and becomes yours to edit. This is the model behind shadcn/ui conventions, where components are React + Tailwind source rather than an opaque dependency.
21st is a registry in the second category. Its library lists 12,000+ crafted React components, templates, and shadcn themes, organized into categories such as 2,000+ marketing blocks (animated heroes, shaders, backgrounds, footers) and 2,100+ UI components (buttons, AI chats, cards and grids, navigation, sign-ins). Components are attributed to named authors, and the site states they ship as prompts you can copy into your tooling — its examples show the same prompt producing a diff in Codex, a file in Claude Code, and a live preview in Lovable. The site also reports 25,431 installs in a week for one component and a builder count of 3,819,076, which gives a rough sense of activity rather than a quality guarantee.
Because these components are source you place in your project, the trade-off is the reverse of a package library: you can edit anything, but you also own maintenance and updates.
Adding a component to your project and verifying it
The exact command depends on the registry, but the shape of the task is consistent. Using a copy-in component as the example:
- Confirm prerequisites. You need a React project with Tailwind and the
cnutility (a class-merging helper) available, since registry components typically import from@/lib/utils. - Get the source. Either run the registry's install command or paste the component's prompt into your AI tool so it writes the file into your components directory. Expect a new file such as
components/ui/shimmer-button.tsx. - Import and render it. Add the import to a page and place the component in the tree.
- Verify. Run your dev server and check that the component appears and responds to interaction. If the registry ships a preview, compare against it.
A minimal check after install:
import { ShimmerButton } from "@/components/ui/shimmer-button";
export default function Page() {
return <ShimmerButton>Get started →</ShimmerButton>;
}
If it renders and the hover/interaction behavior matches the preview, the install succeeded.
Troubleshooting common problems
- Missing imports or unresolved
@/paths. The@alias must be configured in your bundler ortsconfig. If the component imports@/lib/utilsand that file doesn't exist, create it or fix the alias. - Prop type errors. If the component expects a required prop and you omit it, you'll get a warning or a crash. Check the component's signature and pass what it declares.
- Styling looks wrong. Registry components assume Tailwind is set up and that your theme tokens (colors, radii) exist. Missing Tailwind config or theme variables produce unstyled output rather than an error.
- Excessive re-renders. A component re-rendering on every keystroke or parent update usually means state lives too high or a new object/function is created each render. Move state closer to where it's used, or memoize with
useMemo/useCallbackwhen profiling shows it matters. - "Hooks can only be called inside a component." You called a hook at the top level of a module or inside a condition. Hooks must run unconditionally at the top of a component or custom hook.
Choosing between writing and installing
Write your own component when the logic is specific to your app, when you need tight control over behavior, or when a library's abstraction would fight your design. Install from a registry when the UI is generic (buttons, heroes, cards), when you want to read and edit the source, and when you're comfortable owning updates. Use a package library instead when you'd rather receive fixes automatically and don't need to modify internals. The right choice depends on how much control versus maintenance you want — not on which option is universally better.