Most React tutorials begin with a sacred ritual:
“Create a new project in an empty folder.”
But what if you already have something that works? What if you’ve spent hours building a vanilla JavaScript app that fetches JSON, renders a table, and filters data — and you don’t want to delete it just to “do things the React way”?
I didn’t either. So I archived my old files instead of trashing them. And in doing so, I built my first real React app — not as a textbook exercise, but as a practical evolution of something I’d already made.
This is how I did it, what confused me, and why your learning path doesn’t need to match the tutorial script.
From Global Variables to useState: What Actually Changed?
In my vanilla JS version, I had:
|
1 2 3 |
<span class=“token keyword”>let</span> allReadings <span class=“token operator”>=</span> <span class=“token punctuation”>[</span><span class=“token punctuation”>]</span><span class=“token punctuation”>;</span> <span class=“token comment”>// …fetch data → assign to allReadings</span> <span class=“token comment”>// …manually clear table and append rows</span> |
It worked, but the UI and data were loosely connected. In React, I replaced that with two pieces of state:
|
1 2 |
<span class=“token keyword”>const</span> <span class=“token punctuation”>[</span>readings<span class=“token punctuation”>,</span> setReadings<span class=“token punctuation”>]</span> <span class=“token operator”>=</span> <span class=“token function”>useState</span><span class=“token punctuation”>(</span><span class=“token punctuation”>[</span><span class=“token punctuation”>]</span><span class=“token punctuation”>)</span><span class=“token punctuation”>;</span> <span class=“token comment”>// holds all books</span> <span class=“token keyword”>const</span> <span class=“token punctuation”>[</span>statusFilter<span class=“token punctuation”>,</span> setStatusFilter<span class=“token punctuation”>]</span> <span class=“token operator”>=</span> <span class=“token function”>useState</span><span class=“token punctuation”>(</span><span class=“token string”>“all”</span><span class=“token punctuation”>)</span><span class=“token punctuation”>;</span> <span class=“token comment”>// current filter</span> |
And instead of manually updating the DOM, I let React re-render the table whenever either value changed.
The magic isn’t in the syntax — it’s in the mental model shift:
|
1 |
UI = f(state) |
Your interface is a pure function of your application state.
No more innerHTML = "". No more appendChild. Just declare what the table should look like given the current data.
The One JSON Quirk That Tripped Me Up
My JSON structure looked like this:
|
1 2 3 4 5 6 7 8 9 |
<span class=“token punctuation”>{</span> <span class=“token property”>“id”</span><span class=“token operator”>:</span> <span class=“token number”>1</span><span class=“token punctuation”>,</span> <span class=“token property”>“book”</span><span class=“token operator”>:</span> <span class=“token punctuation”>{</span> <span class=“token property”>“title”</span><span class=“token operator”>:</span> <span class=“token string”>“The Pragmatic Programmer”</span><span class=“token punctuation”>,</span> <span class=“token property”>“authors”</span><span class=“token operator”>:</span> <span class=“token punctuation”>[</span><span class=“token string”>“Andrew Hunt”</span><span class=“token punctuation”>,</span> <span class=“token string”>“David Thomas”</span><span class=“token punctuation”>]</span> <span class=“token punctuation”>}</span><span class=“token punctuation”>,</span> <span class=“token property”>“status”</span><span class=“token operator”>:</span> <span class=“token string”>“finished”</span><span class=“token punctuation”>,</span> <span class=“token property”>“rating”</span><span class=“token operator”>:</span> <span class=“token number”>5</span> <span class=“token punctuation”>}</span> |
Notice: title and authors are nested inside book.
In my first render attempt, I wrote:
|
1 |
<span class=“token operator”><</span>td<span class=“token operator”>></span><span class=“token punctuation”>{</span>reading<span class=“token punctuation”>.</span>title<span class=“token punctuation”>}</span><span class=“token operator”><</span><span class=“token operator”>/</span>td<span class=“token operator”>></span> <span class=“token comment”>// ❌ undefined!</span> |
…because the real path is reading.book.title.
Similarly, authors is an array, so I needed:
|
1 |
<span class=“token punctuation”>{</span>reading<span class=“token punctuation”>.</span>book<span class=“token punctuation”>.</span>authors<span class=“token punctuation”>.</span><span class=“token function”>join</span><span class=“token punctuation”>(</span><span class=“token string”>“, “</span><span class=“token punctuation”>)</span><span class=“token punctuation”>}</span> |
💡 Debugging tip: Log one item early:
|
1 |
console<span class=“token punctuation”>.</span><span class=“token function”>log</span><span class=“token punctuation”>(</span><span class=“token string”>“Sample reading:”</span><span class=“token punctuation”>,</span> data<span class=“token punctuation”>[</span><span class=“token number”>0</span><span class=“token punctuation”>]</span><span class=“token punctuation”>)</span><span class=“token punctuation”>;</span> |
This saves 20 minutes of guessing.
Filtering Without Overcomplicating Things
I wanted a dropdown to show “All”, “Reading”, “Finished”, etc.
Instead of storing filtered results in state (a common beginner mistake), I derived them on every render:
|
1 2 3 4 |
<span class=“token keyword”>const</span> filteredReadings <span class=“token operator”>=</span> statusFilter <span class=“token operator”>===</span> <span class=“token string”>“all”</span> <span class=“token operator”>?</span> readings <span class=“token operator”>:</span> readings<span class=“token punctuation”>.</span><span class=“token function”>filter</span><span class=“token punctuation”>(</span><span class=“token parameter”>reading</span> <span class=“token operator”>=></span> reading<span class=“token punctuation”>.</span>status <span class=“token operator”>===</span> statusFilter<span class=“token punctuation”>)</span><span class=“token punctuation”>;</span> |
Why is this better?
-
✅ Less state to manage
-
✅ Always in sync with the source data
-
✅ Zero performance cost for small datasets (<1000 items)
You don’t need useMemo here. Premature optimization is the enemy of clarity.
Then I passed filteredReadings to my <BookTable> component — clean, simple, and testable.
Common Pitfalls Checklist (From My Own Mistakes)
Here’s what went wrong before it worked — so you can skip the frustration:
-
❌ Assuming JSON fields match table columns
→ Always inspect your actual data structure first. -
❌ Trying to render an array directly (
{reading.book.authors})
→ Use.join(", ")for readable author lists. -
❌ Forgetting to capitalize status labels
→"finished"→"Finished"with:1reading<span class=“token punctuation”>.</span>status<span class=“token punctuation”>.</span><span class=“token function”>charAt</span><span class=“token punctuation”>(</span><span class=“token number”>0</span><span class=“token punctuation”>)</span><span class=“token punctuation”>.</span><span class=“token function”>toUpperCase</span><span class=“token punctuation”>(</span><span class=“token punctuation”>)</span> <span class=“token operator”>+</span> reading<span class=“token punctuation”>.</span>status<span class=“token punctuation”>.</span><span class=“token function”>slice</span><span class=“token punctuation”>(</span><span class=“token number”>1</span><span class=“token punctuation”>)</span> -
✅ Using
reading.idas the key in.map()
→ Stable, unique, and required by React. -
✅ Keeping components small (
<StatusFilter>,<BookTable>)
→ Makes testing and reuse easier.
What’s Next? (No Pressure, Just Options)
Now that the core works, here’s where I might go next — when I’m ready:
-
Add interactivity: A “Mark as Finished” button that updates status in state.
-
Mock a backend: Move
readings.jsonbehind a/api/booksroute using Vite’s proxy or a tiny Express server. -
Improve accessibility: Add ARIA labels to the filter dropdown.
-
Style responsibly: Use CSS Modules (already started with
BookTable.css) to avoid global conflicts.
None of these are urgent. The app already does what it needs to do.
Final Thought: Your Setup Doesn’t Have to Be “By the Book”
Tutorials teach idealized workflows. Real learning is messy, non-linear, and full of detours.
I kept my old files. I guessed at folder structures. I didn’t understand “derived state” until after I’d implemented it.
And that’s okay.
What matters isn’t whether you followed the “right” path — it’s that you built something that works, learned from it, and can now share that journey with others who feel just as unsure.
If you’re building your first React app over an existing project — you’re not doing it wrong. You’re doing it realistically.
And that’s worth writing about.
Like this post?
It’s part of my ongoing series on learning full-stack development in public.
Next up: “Why I Stopped Using Elementor — And Started Writing My Own HTML”





Leave a Reply