Getting a Hang Of State In React: A UI Challenge

Posted by N.G. Onike on January 20, 2021 Software Technology

Display  Dynamic  Information  From  One  Column Of  A  Page  To  Another Column  On  The  Same  Page

A while ago I started learning React by applying the technology to personal projects. I soon realised that familiarising myself with its concepts didn’t come without its hurdles.  

I eventually understood how one of Reacts foundational concepts; state, works.  

This article uses solving a challenge to simplify State as a concept in React to a basic form which an intermediate developer and React beginner should understand. I also explore its implementation using props in this tutorial. This article will enable the reader to easily grasp some of the concepts I tackled.

This article hinges on the application of UI in tackling the challenge. To fully grasp this article, one must already be familiar with the set up of React,  HTML, CSS, JSX(JavaScript XML) and JavaScript (JS) ES6+ syntax. However there is still guidance on basic setup herein.

The skill level required to understand this article is listed below.

Skill Level :

  • ReactJS: Beginner
  • Software Development: Intermediate

The predisposed reader should also have encountered:

  • Bash Terminal, Node Package Manager (NPM) and Git

By the end of this article, the ardent reader should understand what State in React is about and even conjure a basic UI implementation using Props.

I go about this article by way of an example. I present a simple challenge I encountered learning React and how going about its solution improved my knowledge of State.  

Challenge  

Enable dynamic display of different information on the left column from varying card components on the right column of the same page on the click of the Image Card/Button.

See Figure 1 below.

                     undefined   

                                                                     Figure 1: Our Goal

React Intro 

React in this case is a technology. A type of software championed by facebook which serves the function of improving display UI(User Interface) and UX(User eXperience). It is a library that facilitates easy storage, retrieval and reuse of UI components across a software project. React is known for its speed as it reduces webpage load time significantly through some software magic I’m not about to go into as that goes beyond our focus.

React is straight forward at this point and is quite popular in development circles.

This challenge is encapsulated within a Sandbox to abstract build irrelevancies. This tutorial will still carry those working in an IDE terminal along.

... and so, we begin our journey at the Terminal.  We can navigate to the folder where we'd like to house our project or head home using the cd command(CMD) -

$ cd /home

it could also be

$ cd /home/desktop

OR 

$ cd /htdocs

depending on where we normally house our projects.

In our parent folder is where the engine begins by the push of the CMD -

$ create React App my-app


this generates a general file structure for the folder my-app (this could be any name you wish) in our chosen IDE as shown below

                      

                    Figure 2: Default Folder Structure

the eventual/final file structure of this sample will turn out like this below

                      

                    Figure 3: Final Folder Structure

This tutorial would be conducted mainly in the /src folder.

Next, we clear out extra generated files irrelevant to this project and strip our scaffold to the bare minimum.

Just what we need. We take out:

  • app.test.js
  • index.css
  • logo.svg 

The bash command to execute this is :

$ rm app.test.js index.css logo.svg

APPROACHING THE SOLUTION

In tackling React, we break down our entire code into components. This facilitates ease of understanding our code by segmenting the relationships between different design features. This little project is uses 2 components: SideBarInfo.js and Thumbnail.js         

Our entry page is the file /src/App.js

These are most of what we need and what we'd be working with.

The stylesheets needed are added to the various folders where their related files are housed; /src and  /Components/                                                                                                                                       

We'd go further into the stylesheets below.

Design

The idea is to be able to click on an Item from the COLUMN 2 on the right and have expanded details about it appear on COLUMN 1 on the left.

Keep the Columns in mind as we'd refer to them throughout this piece.

See figure below

             

                      Figure 4: UI MockUp for our Intended Design

by the design above, we can break down the single page into two columns. This can be achieved by CSS in the stylesheet( styles.css ) . Let's house this in our /src folder

we can create the styles file via the terminal with the CMD

$ touch styles.css

We populate our styles.css file with the design parameters defining our column and classes for the left and the right. This stylesheet would later be invoked in our main page  ../App.js  via JavaScripts import function.

/* styles.css file */

.column {
  padding: 10px;
  height: 250px;
}

.left {
  float: left;
  display: block;
  width: 35%;  
}

.right {
  float: right;
  display: block;
  width: 65%;
}

Components

Now we have our page split in 2 different columns, we can proceed to break down what goes into each column.

The left column ( COLUMN 1 ) will house our first component which is the view for the SideBarInfo which shows details of a clicked item including its corresponding Image and name

from the right column ( COLUMN 2 ) which houses our second component which is the Thumbnail that shows an Item Image and its Name.

Keep in mind that Item List will be stored in an array on our main display page ../App.js  and in this same file is where understanding our Props come into play ( we'd get to this later ).

We navigate to our /src folder and create the folder for our Components  

$ mkdir Components

next we navigate to our /Components  folder where we create the file components and their stylesheet with the CMDs

$ touch SideBarInfo.js Thumbnail.js Sample.css

Thumbnail

We navigate to our ../Components  folder where we focus on the thumbnail component starting with the folder css file ( Sample.css ) which we edit below. This css contains a barebones styling for our Item 

Much ado about naming conventions, we name an Item css class; Work 

furthermore, we'd be referring to Item and Work  interchangeably since Work is how we'll be describing each Item by syntax (more on this later below )                  

Below, we delve into the component stylesheet that defines the parameters of what a single Item in COLUMN 2  should look like. See below.

/* Sample.css file */

.Work .image-container {
  height: 10rem;
  margin-bottom: 1rem;
  border-radius: 30px;
  display: flex;
  flex-wrap: wrap;
  position: inherit;
  width: 100%;
  max-width: 100%;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
  background: #000;
}

Right now, we imagine a single Item to comprise of a thumbnail which houses/displays a single Item Image. Below this item we will display its corresponding Item name Information in a HTML </div>  tag in our Thumbnail.js file.

See figure 5 below      

                 

               Figure 5: Single Thumbnail Item

Done with the stylesheet, we proceed to work on the Thumbnail.js file, where we 

  • import React 
  • import our stylesheet ( Sample.css )
  • define the Work/Item Props
  • placing the Work/Item image in a div and defining its action after a click event
  • placing the Work/item name beneath the image 
import React from "react";  //standard import React function
import "./Sample.css";  // we import the sample stylesheet

//below we  create our thumbnail class for this thumbnail component

class Thumbnail extends React.Component {   
  render() {
    return (
      <div>
        <div className="column right">
          <div className="Work"  
// ES6+ onClick handles the click event for an Item Image 
              onClick={(e) => this.props.click(this.props.work)}  > 

// we display the Item Image with its style class
            <div className="image-container"> 
              <img 
                   src={this.props.work.imageSrc}  
                   alt={this.props.work.imageSrc}  /> 
            </div>
//  display the Items' name info 
            <div className=""> 
              <p> {this.props.work.work}</p>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

// below we export this component making it available for use about the Application

export default Thumbnail;  

Having built the thumbnail Component for column 2, we proceed to build the SidebarBarInfo for COLUMN 1.

SideBarInfo

We navigate back to our /component folder where we face the SideBarInfo.js file which will house the layout for what our COLUMN 1 display will look like.

                         

                          Figure 6: SideBarInfo View

Using the same stylesheet, we embark on defining the UI parameters for the SideBarInfo component below in JSX.

  • we import the React
  • we import the stylesheet
  • we define/display the image style and its prop
  • we display the Item name and further details

/* SideBarInfo.js file */

import React from "react";  // standard import React function
import "./Sample.css";  // imports the Sample stylesheet 

// below we use a function instead of a class(both can be interchangeable depending on usage)

function SidebarInfo(props) {         
  return (
    <div>
      <div className="Work">
        <h1> NAME </h1>            // random header title 
        <div className="image-container"> // image styling here, props below
          <img //image size override, new image dimensions below
            width="150"
            height="150"
            src={props.imageSrc}
            alt={props.imageSrc}
          />
        </div>
        <p> {props.work} </p> // name of the Item/Work props
        <p> {props.view} </p> // other details:"view" of the Item/Work
      </div>
    </div>
  );
}

// lastly we export the entire component file for recall/reuse across our application
export default SidebarInfo;    

App.js

We navigate back to our /src folder  where we turn our attention to the main page where our App component is a class.

You've been reading about state from the start of this write up. It comes into play at this point where we tie our project together. 

What Is State ?

I see State here as a capsule within a component which contains data that is managed within that component. State determines how a component behaves and is rendered(more on render below). Not all components have/use state and those that don't are referred to as stateless components while those that do are stateful. There are many ways a component can access and manage States' data such as the use of props, setState , useState and Hooks. An In-depth explanation is beyond the scope of this tutorial.

Let's dive into how this comes in.

So, our COLUMN 2 holds a list of items and we've designed the container for a single Item ( Thumbnail.js ) . How then do we list the items and get them to show up in our page ?

We begin our App.js file by establishing a Class component (App) with a Constructor method that accepts props, and has this.state which at the moment, is a container housing an empty object.

This empty object would be filled with our array/list of Items and their details.

class App extends React.Component {
        constructor(props) {
          super(props);
          this.state = { };   
        }
   }

The array of Items are to be referred to as : works, while a single item in the array is titled : work. Works is essentially a dummy list of our items. 

works is an array of our Items comprising of :

  • an Id referred to as - ' id'
  • a title known as - 'work'
  • an image link known as - 'imageSrc'
  • one detail known as - 'view'      ( view can hold a link or any string detail )
  • a boolean (true or false) value known as - 'selected'  ( this variable will come into play further below ).

*Note* that you feel free to add more details to your Items(Work) array, but here we use only 2 items.

             screenshot.png  

               Figure 7: View of SIdeBarInfo & Items Array

See what I mean below ( The image links used are sourced from Creative Commons )  

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      works: [
        {
          id: 0,
          work: "Work 1",
          imageSrc:
            "https://images.unsplash.com/photo-1584608168573-b6eec7a04fd7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
          view: "#",
          selected: false
        },
        {
          id: 1,
          work: "Work 2",
          imageSrc:
            "https://images.unsplash.com/photo-1581665269479-57504728e479?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
          view: "#",
          selected: false
        }
      ]
    };
}

Now that we have our array captured in State, we can think about how to get these items showing.

Time to bring in render()  I'd briefly recall a couple of things that render() is and i'd leave out what it's not.

  • The render function performs the role of displaying specified code in an HTML element such as a <div> . 
  • It enables interaction between various components in a React application.                                 
  • Using a Prop, render can divulge full or parts of its State and Components' logic to other components.
  • A render is triggered once there's any change in the components state (part of the magic that makes React fast and responsive)
  • A render can be accessed by the DOM (view layer) and can relay display

In our render function for our App component, we'd display the Column layout and the Items in our State.

We add the code to a render function.

Let's dive

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      works: [
        {
          id: 0,
          work: "Work 1",
          imageSrc:
            "https://images.unsplash.com/photo-1584608168573-b6eec7a04fd7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
          view: "#",
          selected: false
        },
        {
          id: 1,
          work: "Work 2",
          imageSrc:
            "https://images.unsplash.com/photo-1581665269479-57504728e479?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
          view: "#",
          selected: false
        }
      ]
    };

  }

// see the added Render function here below

  render() {
    return (
      <div className="column "> // column style class for this HTML element
        <span>

//NOTE- Reacts class components allows you to call in functions from the same component

// below is SideBarInfo's column (left)            
          <div className="left"> 
                { }    // placeholder for JSX
            </div>  
// below is Thumbnail's column for Items:works array
           <div className="right">  
               { }   // placeholder for JSX
           </div>
        </span>
      </div>
    );
  }
}

Our App component is beginning to fill out. We have our array of items stored in State, we have our Column parameters called in a render function. But we can't see much and have a bug or two.

So, how do we link it all up and display i.e. our stateful App component ( that houses an array of items in state and also renders an empty column arrangement ) + our SidebarInfo functional component (which accepts a prop and returns its data)  +  a single lonely Thumbnail class component ( which renders an empty shell that will contain an Items data ?

*Tip* Class components in React accept props naturally so props doesn't need to be declared but can be called with 'this.props'. Functional components on the other hand have to declare props as in 'function(props)...{props.ObjectName}' (see SideBarInfo.js)

Our COLUMN 2  will house our Items list which would be Thumbnails of the Items currently in our App components state.

Our COLUMN 1 will house a SideBarInfo which can display a clicked Thumbnail and all its data in our state if need be.

We will use 3 functions to go about the logic of linking up our components. All these functions will be in our App component which houses our State. We will call our stylesheet and the other components in our ../App.js   file through JavaScripts import function as well.

The functions we will use are :

  • makeWorks() :  this function iterates through the works array and returns each items data in a thumbnail which listens for a click event
  • handleCardClick() : this function accepts an id and Thumbnail ( React allows you to call a global component in a function provided it has been imported ).                                                                                It listens for a click event and registers the id of the clicked item while changing its selected value based on the event and the storing it in state. 
  • showWorks() :  this functions accepts works , iterates through each one, picks the selected array and displays its data in as a SideBarInfo via props.     

  makeWorks = (works) => {   
  // JavaScripts ES6 map method iterates the array of works and returns an item Thumbnail 
    return works.map((work) => {   
      return (   // returned thumbnail with useful classes and method 
        <Thumbnail
          work={work}
          click={(e) => this.handleCardClick(work.id, e)}
          key={work.id}
        />
      );
    });
  };

// this function below accepts an id and Thumbnail component
  handleCardClick = (id, Thumbnail) => {   
    console.log(id);

// we define works variable below
    let works = [...this.state.works]; 

// our ternary operator below stores the boolean value in the variable
    works[id].selected = works[id].selected ? false : true;   
 
    works.forEach((work) => {  // this ES6 method ensures that unselected values are false
      if (work.id !== id) {
        work.selected = false;
      }
    });

// below we mutate our State by changing the value of selected

    this.setState({   
      works
    });
  };

// vanilla Javascript loops through the works array and uses props to display selected item/work in SideBarInfo

  showWorks = (works) => { 
    let i = 0;
    var w = [];
    while (i < works.length) {
      if (works[i].selected) {
        w = works[i];
      }
      i++;
    }
    return <SidebarInfo imageSrc={w.imageSrc} work={w.work} />;
  };

*Note* Recall that SideBarInfo is a functional component that accepts props. Hence we can call our data from an item in State and use it to populate SideBarInfos shell in one function. We can then return that that function in our render().   [ behold JavaScript Inception ]. Likewise, Thumbnail already defines the work object it accepts via props so when we call it in our App component, we assign it data from our State and allow the component display it. Props are immutable and read-only and cannot be modified or changed like State.

Also, recall the Constructor method ? which accepts props ? Well, our 3 functions shown above are going to go into our App Class and we will bind them to that Constructor method to make state available to these functions. 

below We bind these functions together in state,

call in our component and style imports,

rework our render() to facilitate our display

and call it a day.  

Let's dive

// App.js

// we import components and styling below

import React from "react";
import Thumbnail from "./Components/Thumbnail";     
import SidebarInfo from "./Components/SideBarInfo";
import "./styles.css";

// below we add export method to our App Component 

export default class App extends React.Component { 
  constructor(props) {                         
    super(props);
    this.state = {
      works: [
        {
          id: 0,
          work: "Work 1",
          imageSrc:
            "https://images.unsplash.com/photo-1584608168573-b6eec7a04fd7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
          view: "#",
          selected: false
        },
        {
          id: 1,
          work: "Work 2",
          imageSrc:
            "https://images.unsplash.com/photo-1581665269479-57504728e479?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80",
          view: "#",
          selected: false
        }
      ]
    };

// we bind the functions to our constructor method like this below
    this.makeWorks = this.makeWorks.bind(this);
    this.handleCardClick = this.handleCardClick.bind(this);
    this.showWorks = this.showWorks.bind(this);
  }

// here comes our functions in shining armor
  makeWorks = (works) => {
    return works.map((work) => {
      return (
        <Thumbnail
          work={work}
          click={(e) => this.handleCardClick(work.id, e)}
          key={work.id}
        />
      );
    });
  };

  handleCardClick = (id, Thumbnail) => {
    console.log(id);

    let works = [...this.state.works];

    works[id].selected = works[id].selected ? false : true;

    works.forEach((work) => {
      if (work.id !== id) {
        work.selected = false;
      }
    });

    this.setState({
      works
    });
  };


  showWorks = (works) => {
    let i = 0;
    var w = [];
    while (i < works.length) {
      if (works[i].selected) {
        w = works[i];
      }
      i++;
    }
    return <SidebarInfo imageSrc={w.imageSrc} work={w.work} />;
  };

  render() {                                                           
    return (  
      <div className="column ">
        <span>
// The JSX below renders our showWorks()-it gives it access to state below. This displays SideBarInfo for the Left COLUMN 1
         
           <div className="left">        
              {this.showWorks(this.state.works)} 
            </div>

// The JSX below renders our makeWorks()-it gives it access to state here. It displays item Thumbnails for the right COLUMN 2
          <div className="right">   
              {this.makeWorks(this.state.works)}  
          </div>
        
        </span>
      </div>
    );
  }
}

// export default App; 
// using the export above is an alternative to adding it to the class above

We can now save our files. Your /index.js file should call your App component and should look as below 

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);

RECAP

This piece tackles a UI display challenge. It goes about this via props, State and onClick in React. Our final view would show a UI display akin to Figure 1.

Lastly, this tutorial only displays 2 items. Feel free to add more Items to your array and play around with the CSS to accommodate your own styling. 

You can view a working version of this tutorial in my SandBox 

QUICK-SHARE :