Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Components & Props
Components are the building blocks of any React application. They let you split the UI into independent, reusable pieces, and think about each piece in isolation. The LEGO analogy: A LEGO set gives you individual bricks (components) that each serve a purpose — a window block, a roof tile, a door. You can combine them to build a house, rearrange them to build a spaceship, or reuse the same window block in both. React components work the same way: aButton, a Header, a Card — each is self-contained, reusable, and composable. The difference is that React components can accept data (props) that change how they look and behave, like a LEGO brick that changes color based on what you tell it.
Component Mental Model
Think of components like LEGO blocks:Functional Components
In modern React, we primarily use Functional Components. They are simply JavaScript functions that return JSX.Component Naming Rules
Importing and Exporting Components
Props (Properties)
Props are the way to pass data from parent to child components. Think of them as function arguments — because that is literally what they are. A React component is a function, and props are its parameters. Just like you would callgreet("Alice") to pass a name to a function, you write <Welcome name="Alice" /> to pass a name to a component.
Passing Props
Receiving Props
The component receives all props as a single object.Props Are Read-Only (Immutable)
Default Props
You can provide default values for props when they’re not passed.Children Prop
children is a special prop that allows you to pass components or content between the opening and closing tags.
Practical Children Patterns
Prop Types (Runtime Validation)
PropTypes provide runtime type checking for your components.Common PropTypes
| PropType | Description |
|---|---|
PropTypes.string | String value |
PropTypes.number | Number value |
PropTypes.bool | Boolean value |
PropTypes.array | Array |
PropTypes.object | Object |
PropTypes.func | Function |
PropTypes.node | Anything renderable (string, number, element) |
PropTypes.element | React element |
PropTypes.oneOf(['a', 'b']) | One of specific values |
PropTypes.arrayOf(PropTypes.number) | Array of specific type |
PropTypes.shape({ name: PropTypes.string }) | Object with specific shape |
Component Composition Patterns
1. Container and Presentational Components
Separate logic from presentation:2. Compound Components
Components that work together to form a complete UI:3. Render Props
Pass a function as a child to share logic:Spreading Props
You can spread all props to a child component:🎯 Practice Exercises
Exercise 1: Create a Reusable Alert Component
Exercise 1: Create a Reusable Alert Component
Build an
Alert component that accepts:type: ‘success’ | ‘warning’ | ‘error’title: stringchildren: content
Exercise 2: Build a Card System
Exercise 2: Build a Card System
Create a flexible card system with
Card, Card.Header, Card.Body, and Card.Footer:Exercise 3: Avatar Component with Fallback
Exercise 3: Avatar Component with Fallback
Create an Avatar component that shows initials if no image is provided:
Summary
| Concept | Description |
|---|---|
| Functional Components | Functions that return JSX |
| Props | Arguments passed to components (read-only) |
| Destructuring | Extract props for cleaner code |
| Default Props | Fallback values when props aren’t provided |
| children | Special prop for nested content |
| PropTypes | Runtime type checking |
| Composition | Building complex UIs from simple components |
| Spreading Props | Pass all props with {...props} |
Next Steps
In the next chapter, you’ll learn about State & useState — how to make your components interactive and dynamic!
Interview Deep-Dive
What is the difference between the 'children' prop and render props? When would you choose one over the other?
What is the difference between the 'children' prop and render props? When would you choose one over the other?
Strong Answer:
The
children prop passes static JSX content between a component’s opening and closing tags. The parent decides what to render, and the wrapper component just places it somewhere in its layout. It is the simplest composition pattern — think of it like a picture frame: the frame does not care what picture you put in it.Render props (including function-as-children) are fundamentally different because the parent component passes a function that receives data and returns JSX. This means the wrapper component can share its internal state or computed values with the consumer without exposing them through Context or lifting state up. The classic example is a MouseTracker that tracks cursor position internally and passes { x, y } to the render function so the consumer can decide how to display it.In practice, I reach for children when the wrapper is a layout concern (cards, modals, page shells) and does not need to pass data back. I use render props when the wrapper owns logic that the consumer needs access to — for instance, a Formik component that manages form state and passes { values, errors, handleChange } to a render function.That said, since hooks arrived, most render prop patterns have been replaced by custom hooks. Instead of a <MouseTracker> render prop, you write useMouse() and get { x, y } directly. Hooks are more composable and avoid the nesting pyramid that render props create. The only place render props still shine is when you need to conditionally render children based on the wrapper’s internal state and the wrapper also controls DOM structure (like a headless dropdown component).Follow-up: The compound components pattern uses React.Children.map and React.cloneElement. What are the downsides of this approach and what is the modern alternative?React.Children.map and cloneElement are fragile. They only work if the compound children are direct children — if someone wraps them in a div or a fragment, cloneElement injects props into the wrong element. They also break TypeScript’s type safety because the injected props are invisible to the type system. And they create an implicit API contract: the parent “magically” injects props that the child expects but never declares.The modern alternative is to use Context. The parent compound component (like Accordion) provides state through a Context, and each child (AccordionItem) consumes it with useContext. This works regardless of nesting depth, plays well with TypeScript, and makes the data flow explicit. Libraries like Radix UI and Headless UI use this pattern extensively.The tradeoff is slightly more boilerplate (creating a Context) and the performance implications of Context re-renders. But for compound components with a handful of children, this is negligible.A junior developer on your team puts all component logic in one giant component. How do you explain when and why to split components?
A junior developer on your team puts all component logic in one giant component. How do you explain when and why to split components?
Strong Answer:
I would start with concrete signals rather than abstract principles. You should split a component when you see any of these:First, the component does two unrelated things. If a component fetches user data AND renders a chart, those are two concerns. The data fetching should live in a container or custom hook, and the chart should be its own presentational component. The test for this: can you describe what the component does in one sentence without using “and”?Second, you see duplicated JSX patterns. If three pages each render a card with an avatar, name, and action button, extract a
UserCard component. The rule of thumb is: if you copy-paste JSX more than once, extract it.Third, the component is hard to test. If you need to mock five APIs and set up complex state just to test whether a button turns blue, the component is doing too much. Smaller components have smaller test surfaces.Fourth, the file exceeds roughly 200-300 lines. This is not a hard rule, but if you are scrolling a lot, it is a smell.What I would not do is split prematurely. A component that renders a form with five fields does not need five separate FormField components on day one. Over-splitting creates indirection without benefit — you end up jumping between 15 files to understand one screen. I follow the “rule of three”: the first time you write something, just write it. The second time, note the duplication. The third time, extract it.The key mental model is: components are functions, and the same principles apply. A function should do one thing, be testable in isolation, and have a clear interface (props). If your component violates any of these, it is a candidate for splitting.Follow-up: How do you decide whether shared logic should be a component, a custom hook, or a utility function?If the shared logic involves JSX output, it is a component. If it involves React state or lifecycle (hooks), it is a custom hook. If it is pure data transformation with no React dependency, it is a utility function.Concrete example: formatting a date is a utility function — formatDate(timestamp). Tracking window dimensions with a resize listener is a custom hook — useWindowSize(). Rendering a formatted timestamp with a tooltip showing the relative time is a component — <Timestamp value={date} />.The mistake I see most often is putting pure logic into hooks unnecessarily. If your “hook” does not call useState, useEffect, or another hook, it should not be a hook at all — it should be a plain function. Making it a hook adds the Rules of Hooks constraints for no benefit.Explain the concept of props immutability in React. Why can you not modify props, and what would actually happen if you tried?
Explain the concept of props immutability in React. Why can you not modify props, and what would actually happen if you tried?
Strong Answer:
Props are read-only because React’s rendering model depends on unidirectional data flow: data flows down from parent to child, and the parent is the single source of truth for any prop value. If a child could mutate its props, the parent’s state and the child’s view of that state would diverge, leading to inconsistencies that are extremely hard to debug.If you literally do
props.name = 'changed' in JavaScript strict mode (which React enforces in development), you get a TypeError because React freezes the props object with Object.freeze() in development mode. In production builds, Object.freeze is stripped for performance, so the mutation would silently succeed at the JavaScript level — but React would not know about it and would not re-render. The UI would be stale, or worse, you would corrupt shared object references if the parent passes the same object to multiple children.The deeper principle is that React components should be pure functions of their props and state. Given the same props, a component should produce the same output. Mutating props violates this contract and makes components unpredictable, untestable, and impossible to optimize with React.memo (which relies on shallow comparison of the previous and next props).In practice, if you need to “change” a prop, you have two options: lift the state up to the parent and pass a callback (onNameChange), or use local state initialized from the prop (useState(props.initialName)). The second approach is common for “initial value” props where the child needs to diverge from the parent’s value (like a form input with a default).Follow-up: What is the difference between passing initialValue as a prop versus value as a prop? How does this affect component design?This is the distinction between controlled and uncontrolled components. When you pass value, the parent owns the state and the component must call back via onChange to request changes — the parent is in full control. When you pass initialValue, the component copies it into local state on mount and manages it independently from that point.The tradeoff: controlled components give the parent full authority over the value (useful for validation, formatting, dependent fields) but require the parent to manage state for every input. Uncontrolled components are simpler for the parent but harder to synchronize if the parent later needs to reset or override the value.A common bug with initialValue is expecting the component to update when the prop changes after mount. Since useState(initialValue) only reads the initial value once, changing the prop later has no effect. If you need to reset, you either need a key change to remount the component or a useEffect that syncs the prop to state — which is an anti-pattern the React docs explicitly warn against. The cleanest solution is using key={userId} on the component so React unmounts and remounts it with fresh state when the user changes.