Building Poor Man's React

Hey there! Ever wondered how React actually works under the hood? I know I did! That's why I decided to build "Poor Man's React" - a super simplified version of React that helped me understand its core concepts. This little project was inspired by Tejas Kumar's talk at React Berlin Conference, and I was lucky enough to meet him at React India Conference! Let me share what I learned along the way.

What's Poor Man's React Anyway?

Think of Poor Man's React as a "React for dummies" - it's a stripped-down version that implements just enough of React's core features to help you understand how the magic happens. It's like taking apart a watch to see how the gears work together, but for React!

What I Built (The Fun Parts!)

I focused on three main features that I found interesting:

  1. createElement: The thing that turns JSX into something React can work with
  2. useState: How React manages state in functional components
  3. Concurrent Loading: A simple way to handle API calls (kinda like React Suspense, but way simpler)

Let's dive into each one and see how they work!

1. The createElement Function

This is where the magic starts. When you write JSX, it gets transformed into createElement calls behind the scenes:

let React = {
  createElement: (tag, props, ...children) => {
    if (typeof tag == 'function') {
      try {
        return tag(props)
      } catch ({ promise, key }) {
        promise.then((data) => {
          promiseCache.set(key, data)
          rerender()
        })
        return { tag: 'h1', props: { children: ['I am loading'] } }
      }
    }
    var element = { tag, props: { ...props, children } }
    return element
  },
}

What this does:

  • Handles both regular HTML elements and functional components
  • Shows a loading state while waiting for async data
  • Creates a simple virtual DOM structure

2. State Management with useState

Here's my attempt at implementing useState - it's way simpler than the real thing, but it helped me understand the concept:

const states = []
let stateCursor = 0

const useState = (initialState) => {
  const FROZEN_STATE = stateCursor
  states[FROZEN_STATE] = states[FROZEN_STATE] || initialState
  const setState = (newState) => {
    states[FROZEN_STATE] = newState
    rerender()
  }
  stateCursor++
  return [states[FROZEN_STATE], setState]
}

The cool thing about this:

  • It uses an array to store all state values (React does something similar internally)
  • It keeps track of which state belongs to which component
  • When state changes, it triggers a re-render

3. Concurrent Loading with createResource

This is my simplified version of React's Suspense feature:

const promiseCache = new Map()
const createResource = (returningFunction, key) => {
  if (promiseCache.has(key)) return promiseCache.get(key)
  throw { promise: returningFunction(), key }
}

What this does:

  • Caches API responses so we don't fetch the same data twice
  • Shows a loading state while waiting for data
  • Handles async data in a React-like way

Putting It All Together

Here's a simple component that uses all these features:

const App = () => {
  const [name, setName] = useState('Pranesh')
  const [count, setCount] = useState(0)
  const dogPhotoUrl = createResource(
    () =>
      fetch('https://dog.ceo/api/breeds/image/random')
        .then((res) => res.json())
        .then((payload) => payload.message),
    'dogPhoto',
  )

  return (
    <div className="hello">
      <h1>Hello {name}!</h1>
      <input
        value={name}
        onchange={(e) => setName(e.target.value)}
        type="text"
        placeholder="name"
      />
      <h2>Count: {count}</h2>
      <button onclick={(e) => setCount(count + 1)}>+</button>
      <img src={dogPhotoUrl} />
      <p>Welcome to Deconstructed React!</p>
    </div>
  )
}

The Rendering Process

This is how we turn our virtual DOM into actual DOM elements:

const render = (element, container) => {
  const DOMElement = document.createElement(element?.tag)
  if (element?.props) {
    Object.keys(element?.props)
      .filter((key) => !['children', '__self', '__source'].includes(key))
      .forEach((p) => {
        DOMElement[p] = element.props[p]
      })
  }
  if (element?.props?.children) {
    element.props.children.forEach((child) => {
      render(child, DOMElement)
    })
  }
  if (['string', 'number'].includes(typeof element)) {
    container.appendChild(document.createTextNode(element))
    return
  }
  container.appendChild(DOMElement)
}

What I Learned

This little project taught me a lot about React:

  1. React's Core Principles: Now I understand better how React manages state and renders updates.

  2. Virtual DOM: I got to see how the virtual DOM works in a simplified way.

  3. Concurrent Features: I learned a bit about how React handles async operations.

  4. State Management: I now have a better idea of how React maintains component state.

Meeting Tejas Kumar

One of the coolest parts of this journey was meeting Tejas Kumar at React India Conference. His talk at React Berlin Conference inspired this project, and getting to chat with him about it in person was awesome! It's pretty cool how the React community brings developers together to share knowledge.

Wrapping Up

Building Poor Man's React was a fun way to understand React's internals better. It's definitely not a production-ready library, but it helped me grasp the core concepts that make React powerful. If you're curious about how React works under the hood, I'd definitely recommend trying something like this!

Remember, this is just a learning exercise. The real React codebase is way more complex with lots of optimizations and features. But understanding these basics helps you appreciate and use React better in your projects.

Happy coding! 🚀