{"id":"l-rf-2-1","courseId":"react-fundamentals","lang":"en","kind":"lesson","summary":"Turn a screen mockup into a component tree, build a static version first, and identify the minimal state the Watchlist needs before writing any interactive code.","html":"<p>Here’s the Watchlist you’re building, described as a designer would hand it over: a header with the title and “3 to watch”; a form with a title input and an Add button; a row of filter buttons (All, To watch, Watched); and a list of movies, each with its title, year and a checkbox to mark it watched. When the list is empty, a friendly message replaces it.</p>\n<p>Before you write a line of code, decide what the components are. It’s the most important design step in a React app, and it’s done with a pen.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Draw boxes, name them</h2>\n<p>Take the mockup and draw a box around every piece that does one job. Then name each box as a noun, the way you’d name a function.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 300\" role=\"img\" aria-labelledby=\"l-rf-2-1-t1\"><defs><marker id=\"arrow-l-rf-2-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-rf-2-1-t1\">App is the root. Its children are Header, AddMovieForm, FilterBar and MovieList. Header contains WatchCount. MovieList contains several MovieItem components, or EmptyState when there are no movies.</title>\n  <rect class=\"d-box-primary\" x=\"290\" y=\"16\" width=\"120\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"44\" text-anchor=\"middle\">App</text>\n  <rect class=\"d-box\" x=\"20\" y=\"120\" width=\"130\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"148\" text-anchor=\"middle\">Header</text>\n  <rect class=\"d-box\" x=\"190\" y=\"120\" width=\"150\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"265\" y=\"148\" text-anchor=\"middle\">AddMovieForm</text>\n  <rect class=\"d-box\" x=\"380\" y=\"120\" width=\"120\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"440\" y=\"148\" text-anchor=\"middle\">FilterBar</text>\n  <rect class=\"d-box-accent\" x=\"540\" y=\"120\" width=\"140\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"610\" y=\"148\" text-anchor=\"middle\">MovieList</text>\n  <rect class=\"d-box\" x=\"20\" y=\"230\" width=\"130\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"85\" y=\"258\" text-anchor=\"middle\">WatchCount</text>\n  <rect class=\"d-box\" x=\"440\" y=\"230\" width=\"120\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"500\" y=\"258\" text-anchor=\"middle\">MovieItem ×n</text>\n  <rect class=\"d-box\" x=\"575\" y=\"230\" width=\"115\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"632\" y=\"258\" text-anchor=\"middle\">EmptyState</text>\n  <path class=\"d-line\" d=\"M350 60 L85 120\"/>\n  <path class=\"d-line\" d=\"M350 60 L265 120\"/>\n  <path class=\"d-line\" d=\"M350 60 L440 120\"/>\n  <path class=\"d-line\" d=\"M350 60 L610 120\"/>\n  <path class=\"d-line\" d=\"M85 164 L85 230\"/>\n  <path class=\"d-line\" d=\"M610 164 L500 230\"/>\n  <path class=\"d-line d-dashed\" d=\"M610 164 L632 230\"/>\n</svg><figcaption>The Watchlist as a component tree</figcaption></figure>\n<p>Nested boxes become a tree. <code dir=\"ltr\">App</code> owns the whole screen. <code dir=\"ltr\">MovieList</code> renders either many <code dir=\"ltr\">MovieItem</code>s or one <code dir=\"ltr\">EmptyState</code>. This tree is the plan for the rest of the course; you’ll build each box as a component.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">When to split, and when not to</h2>\n<p>Split a piece into its own component when at least one of these is true:</p>\n<ul>\n<li><strong>It repeats.</strong> <code dir=\"ltr\">MovieItem</code> appears once per movie. Writing it once is the point.</li>\n<li><strong>It has its own job.</strong> <code dir=\"ltr\">AddMovieForm</code> handles typing and submitting. Nothing else needs to know how.</li>\n<li><strong>The parent is getting hard to read.</strong> If you scroll to understand a component, extract a named piece.</li>\n</ul>\n<p>Don’t split just because you can. A component that only wraps one <code dir=\"ltr\">&lt;span&gt;</code> adds a file, an import and a name to remember, and gives nothing back. Experienced teams often keep a component at 100 lines rather than splitting it into five 20-line ones that are only used together. Start a little bigger and extract when one of the three reasons appears.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Name by role, not by look</span></p>\n<p><code dir=\"ltr\">FilterBar</code> and <code dir=\"ltr\">WatchCount</code> describe what the pieces are for. <code dir=\"ltr\">BlueButtonRow</code> and <code dir=\"ltr\">BigNumber</code> stop being accurate the day the design changes.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Build the static version first</h2>\n<p>With the tree drawn, build it with hard-coded data and no interactivity. Pass data down from the top so the shape is right from the start:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"jsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/App.jsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> MOVIES</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Arrival'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2016</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Past Lives'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2023</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { id: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Paddington 2'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, year: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2017</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, watched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> App</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Header</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movies</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">MOVIES</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">AddMovieForm</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">FilterBar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">MovieList</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> movies</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">MOVIES</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">main</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// …Header, AddMovieForm, FilterBar and MovieList defined below</span></span></code></pre></div>\n<p>Building the static version is mostly typing, and it gets the structure and markup right while nothing moves. Adding interactivity is mostly thinking. Doing both at once is how components end up tangled.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Find the minimal state</h2>\n<p>Next, list every piece of data in the app and ask three questions about each. Does it stay the same over time? Is it passed in from a parent? Can you calculate it from other data? If the answer to any of them is yes, it isn’t state.</p>\n<p>For the Watchlist:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Data</th>\n<th>State?</th>\n<th>Why</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>The list of movies</td>\n<td>Yes</td>\n<td>Changes when you add, remove or mark one</td>\n</tr>\n<tr>\n<td>The current filter</td>\n<td>Yes</td>\n<td>Changes when you click a filter button</td>\n</tr>\n<tr>\n<td>Text typed in the form</td>\n<td>Yes</td>\n<td>Changes as you type</td>\n</tr>\n<tr>\n<td>Number left to watch</td>\n<td>No</td>\n<td>Calculated from the movies</td>\n</tr>\n<tr>\n<td>Movies currently visible</td>\n<td>No</td>\n<td>Calculated from movies + filter</td>\n</tr>\n</tbody>\n</table></div><p>Three pieces of state, two derived values. Keeping state minimal is the single habit that prevents the most bugs in React apps. Every extra piece of state is a second copy of the truth that can disagree with the first.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Storing what you can calculate</span></p>\n<p>Beginners often keep <code dir=\"ltr\">visibleMovies</code> in state next to <code dir=\"ltr\">movies</code> and <code dir=\"ltr\">filter</code>, then forget to update it when a movie is added. The list on screen silently goes stale. If a value can be computed from state you already have, compute it during render.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">Where the data lives</h2>\n<p>The movies are needed by <code dir=\"ltr\">Header</code> (for the count) and <code dir=\"ltr\">MovieList</code> (to show them). The rule: data lives in the closest common parent of every component that needs it, which here is <code dir=\"ltr\">App</code>. It flows down as props. When a child needs to change it, the parent passes down a function to call. You’ll do exactly that in Section 3.</p>\n<p>Next, you’ll learn props properly: how to pass data into components, set defaults and keep components reusable.</p>\n","toc":[{"id":"sec-1","text":"Draw boxes, name them","level":2},{"id":"sec-2","text":"When to split, and when not to","level":2},{"id":"sec-3","text":"Build the static version first","level":2},{"id":"sec-4","text":"Find the minimal state","level":2},{"id":"sec-5","text":"Where the data lives","level":2}],"takeaways":["Draw boxes around the mockup and name each box; nested boxes become a component tree.","Split a component when a piece is reused, has its own job, or makes the parent hard to read, not by default.","Build a static version from hard-coded data first, then add interactivity.","Keep the minimal state; anything you can calculate from other data, such as a count or a filtered list, is derived, not state."],"further":[{"title":"Thinking in React","url":"https://react.dev/learn/thinking-in-react"},{"title":"Your UI as a Tree","url":"https://react.dev/learn/understanding-your-ui-as-a-tree"}],"quiz":[{"q":"<p>The Watchlist needs to show how many movies are unwatched. Where should that number come from?</p>","options":[{"text":"A separate <code dir=\"ltr\">unwatchedCount</code> state that every handler updates.","why":"A second copy of information that’s already in the list will eventually disagree with it. That’s the bug React is supposed to remove."},{"text":"Calculated from the movies array while rendering, with <code dir=\"ltr\">movies.filter((m) =&gt; !m.watched).length</code>.","why":"Correct. It’s derived data. Calculating it on each render keeps it correct automatically, and for a list this size it costs nothing."},{"text":"Stored in localStorage and read back on every render.","why":"localStorage is for persisting data across reloads, not for values you can compute from data you already have."}],"answer":1},{"q":"<p>A teammate splits the movie card into <code dir=\"ltr\">MovieTitle</code>, <code dir=\"ltr\">MovieYear</code>, <code dir=\"ltr\">MovieTitleWrapper</code> and <code dir=\"ltr\">MovieYearText</code>, each wrapping one tag. What’s the best review comment?</p>","options":[{"text":"Good, smaller is always better in React.","why":"Tiny components that do nothing but wrap one tag add files and indirection without adding reuse or clarity."},{"text":"Use class components for the small ones.","why":"Function components are the standard; the class/function choice isn’t the issue here."},{"text":"Merge them into one component, because React can only render one component per file.","why":"A file can define and render any number of components. The problem is unnecessary splitting, not a file rule."},{"text":"Keep one MovieCard until a piece is reused or grows its own logic; these splits add indirection without benefit.","why":"Correct. Split for reuse, for a clear separate job, or for readability. Wrapping single tags makes the code harder to follow."}],"answer":3},{"q":"<p>In the Watchlist tree, <code dir=\"ltr\">App</code> contains <code dir=\"ltr\">Header</code>, <code dir=\"ltr\">AddMovieForm</code>, <code dir=\"ltr\">FilterBar</code> and <code dir=\"ltr\">MovieList</code>, and <code dir=\"ltr\">MovieList</code> contains many <code dir=\"ltr\">MovieItem</code>s. Which components need the movies array?</p>","options":[{"text":"Header (for the count) and MovieList (to show the movies), so it should live in their common parent, App.","why":"Correct. Data that several components need lives in their closest common parent and flows down as props."},{"text":"Only MovieItem, because it’s the one that displays a movie.","why":"Each MovieItem needs one movie, but the Header needs the whole list to count it, and MovieList needs it to render items."},{"text":"Every component, so each one should keep its own copy.","why":"Copies drift apart. One owner with props flowing down keeps everything consistent."},{"text":"FilterBar, because filtering decides what’s visible.","why":"FilterBar needs the current filter value, not the movies. MovieList is what combines the two."}],"answer":0},{"q":"<p>Why build a static version of the Watchlist with hard-coded data before adding state?</p>","options":[{"text":"React can’t render components that have state until a static version exists.","why":"There’s no such rule. It’s a working method, not a technical requirement."},{"text":"Static components render faster in production.","why":"Speed isn’t the reason. The order of work is about separating two different kinds of thinking."},{"text":"It separates layout and structure from interactivity, so you get the tree right before thinking about what changes.","why":"Correct. Building the static version is mostly typing; deciding on state needs careful thought. Doing them one at a time avoids tangling both."}],"answer":2}],"exercise":{"kind":"choice","multiple":true,"options":["ratings","average-rating","sort-order","sorted-movies","is-sorted-by-year"],"correct":["ratings","sort-order"],"title":"Find the minimal state","prompt":"<p>The Watchlist gets two new features. Each movie can now have a star rating from 1 to 5 that you set by clicking stars, and a dropdown lets you sort the list by <strong>title</strong> or by <strong>year</strong>. The header also shows the average rating of the movies you’ve watched.</p>\n<p><strong>Which of these should be stored in state?</strong> Pick every one that is truly state; leave out anything that’s derived.</p>\n","hints":["For each item, ask: can I calculate this from other data I already have?","An average, a sorted copy and a yes/no about the sort can all be computed from the ratings and the chosen sort order."],"explanation":"<p>Only two things change on their own because of what the user does: each movie’s rating (stored on the movie in the movies state) and the chosen sort order. Everything else is calculated during render. The average is <code dir=\"ltr\">watched ratings ÷ count</code>, the sorted list is a sorted copy of the movies based on the sort order, and “is sorted by year” is just <code dir=\"ltr\">sortOrder === 'year'</code>. Storing any of those would create a second copy that can go stale.</p>\n","checkLabels":{},"optionText":{"ratings":{"text":"Each movie’s rating, stored as a field on the movie in the movies state","why":"Correct. A rating changes when the user clicks a star and can’t be computed from anything else."},"average-rating":{"text":"The average rating shown in the header","why":"Derived. Compute it from the watched movies’ ratings during render, or it will drift when a rating changes."},"sort-order":{"text":"The selected sort order: “title” or “year”","why":"Correct. It changes when the user picks from the dropdown, and nothing else can tell you what they picked."},"sorted-movies":{"text":"A sorted copy of the movie list","why":"Derived. Sort a copy of the movies by the current sort order while rendering; storing it means re-sorting by hand after every change."},"is-sorted-by-year":{"text":"A boolean <code dir=\"ltr\">isSortedByYear</code> used to style the dropdown","why":"Derived. It’s <code dir=\"ltr\">sortOrder === 'year'</code>. Keeping it in its own state lets it disagree with the actual sort order."}}},"words":674,"readMinutes":3}