sajad torkamani

Note: taken mostly from the official docs.

Suppose you’ve been given a new design mock-up like the below and tasked with building it out as an interactive web page.

Here are the steps you can take to turn that mock-up into an interactive web page using React.

1. Break the UI into a component hierarchy

You can either:

  1. Draw boxes around each component and name them.
  2. Create a bullet point list of all the components. Nest the bullet points if needed.
1. Break the UI into a component hierarchy

You want to end up with a nested list like this:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

2. Build a static version in React 

Build your components using static / hard-coded data. At this stage, you want to focus on building reusable components that can render the static data. You can pass data from parent to child using props, but don’t worry about state or interactivity at this point. You shouldn’t use useState at this point.

You can take one of two approaches:

  1. Top-down: Start with building the components higher up in the hierarchy (e.g., FilterableProductTable). This approach is usually easier for simpler UIs.
  2. Bottom-up: Start from components lower down (e.g., ProductRow). Usually more suitable for more complex UIs.

If you take the bottom-up approach, your code at this stage might look like this:

function ProductCategoryRow({ category }) {
  return (
    <tr>
      <th colSpan="2">
        {category}
      </th>
    </tr>
  );
}

function ProductRow({ product }) {
  const name = product.stocked ? product.name :
    <span style={{ color: 'red' }}>
      {product.name}
    </span>;

  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  );
}

function ProductTable({ products }) {
  const rows = [];
  let lastCategory = null;

  products.forEach((product) => {
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow
          category={product.category}
          key={product.category} />
      );
    }
    rows.push(
      <ProductRow
        product={product}
        key={product.name} />
    );
    lastCategory = product.category;
  });

  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
}

function SearchBar() {
  return (
    <form>
      <input type="text" placeholder="Search..." />
      <label>
        <input type="checkbox" />
        {' '}
        Only show products in stock
      </label>
    </form>
  );
}

function FilterableProductTable({ products }) {
  return (
    <div>
      <SearchBar />
      <ProductTable products={products} />
    </div>
  );
}

const PRODUCTS = [
  {category: "Fruits", price: "$1", stocked: true, name: "Apple"},
  {category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"},
  {category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"},
  {category: "Vegetables", price: "$2", stocked: true, name: "Spinach"},
  {category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"},
  {category: "Vegetables", price: "$1", stocked: true, name: "Peas"}
];

export default function App() {
  return <FilterableProductTable products={PRODUCTS} />;
}

Note how you first create the components lower down in the hierarchy and work your way up to the top. At this stage, it can be helpful to create all these components in the same file. You can extract them to separate files later on, once you’ve developed a better feel for the component hierarchy.

Usually, you’ll want to have your component at the top of the hierarchy (e.g., FilterableProductTable) take your data model as a prop, and pass that data down to components further down.

This is called one-way data flow because the data flows from the top-level components down to the components at the bottom of the tree.

3. Find the minimal but complete representation of UI state 

To make the UI interactive, you need to let users change your underlying data model. You’ll need state for this.

Think of state as the minimal set of changing data that your app needs to remember. Figure out the minimal representation of state that your UI needs and compute everything else on-demand.

For instance, for the products table example, you can store the list of products in state and then compute other data based on that (e.g., number of products). The less state you manage, the simpler your code will likely be.

List all the pieces of data in the UI

For example:

  1. The original list of products
  2. The value in the search box
  3. The value of Only show products in stock checkbox
  4. The filtered list of products

Identify which pieces of data are state

For each item from your data list, ask yourself:

  • Does it remain unchanged over time? If so, it’s not state.
  • Is it passed in from a parent component via props? If so, it isn’t state.
  • Can you compute it based on other state or props? If so, it isn’t state.

What is left is probably state. Here is how you’d apply these questions to the products table UI:

  • The original list of products – It’s passed in as props, so it’s not state.
  • The value in the search box – It changes over time, it’s not passed in via props, and it can’t be computed based on other state or props. It is state.
  • The value of the Only show products in stock checkbox – It changes over time, it’s not passed in via props, and it can’t be computed based on other state or props. It is state.
  • The filtered list of products – It can be computed by filtering the original list of products according to the search box and checkbox inputs. It is not state.

4: Identify where your state should live 

Once you’ve identified your UI’s minimal state, you need to identify which component is responsible for changing the state, or owns the state.

For each piece of state in your UI:

  1. Identify every component that renders something based on that state.
  2. Find their closest common parent component – a component above them all in the hierarchy.
  3. Decide where to put the state:
    1. Often, you can put the state in the common parent component.
    2. You can also put the state into some component above the common parent component.
    3. If it’s not clear what common parent component should own the state, create a new component solely for holding the state and place it somewhere above the common parent component.

For the example products table UI, you have the searchbox value and the checkbox as state, so you can take the following steps to identify where that state should live:

  1. Identify components that use state
    1. ProductsTable needs to filter the product list based on both the search text and checkbox value).
    2. SearchBar needs to display the value of the search text and checkbox value.
  2. Finder their closest common parent
    1. The closest common parent they share is FilterableProductTable.
  3. Decide where to put the state
    1. FilterableProductTable

Now that you know what state your UI needs and where it should live, you can use useState to add them to FilterableProductsTable and to define the initial states:

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

Then, embracing React’s one-way data flow, you can pass these state variables down to ProductsTable and SearchBar as props:

<div>
  <SearchBar 
    filterText={filterText} 
    inStockOnly={inStockOnly} />
  <ProductTable 
    products={products}
    filterText={filterText}
    inStockOnly={inStockOnly} />
</div>

At this point, your UI renders correctly with state and props flowing down the hierarchy. But to change the state (e.g., search text or checkbox value), you’ll need to support data flowing the other way. The form components deep in the hierarchy need to update the state that lives in FilterableProductsTable.

For example, the SearchBar input needs to have an onChange handler that calls setFilterText, but at the moment it doesn’t:

function SearchBar({ filterText, inStockOnly }) {
  return (
    <form>
      <input 
        type="text" 
        value={filterText} 
        placeholder="Search..."/>

5: Add inverse data flow 

The filterText and inStockOnly states are owned by FilterableProductTable, so only it can call setFilterText and setInStockOnly. To allow SearchBar or other child components update the state in FilterableProductTable, you must pass the state updating functions (setFilterText and setInStockOnly) down to SearchBar.

function FilterableProductTable({ products }) {
  const [filterText, setFilterText] = useState('');
  const [inStockOnly, setInStockOnly] = useState(false);

  return (
    <div>
      <SearchBar 
        filterText={filterText} 
        inStockOnly={inStockOnly}
        onFilterTextChange={setFilterText}
        onInStockOnlyChange={setInStockOnly} />

Inside SearchBar, you can add an onChange event handler that uses those state-updating functions to update the parent state:

<input 
  type="text" 
  value={filterText} 
  placeholder="Search..." 
  onChange={(e) => onFilterTextChange(e.target.value)} />

Now, you’ve successfully turned the design mock into an interactive UI.

Sources

Leave a comment

Your email address will not be published. Required fields are marked *