React state

In 1.3.1 we identified that it could be tricky to fetch the image source and other image information from the images array. There is enough going on turning that data into an image that it would warrant it's own component. Thats what we are going to do in this session.

Learning Objectives

State

State in React is very important concept to grasp. A component can have many different 'states'. For example an input field can be empty, thats one 'state'. It may not be empty, that is another 'state', it could have invalid values, it could have default values, it could have pre-loaded values.

Many states.

React is absolutely wonderful at handling state for you. We hacked the displaying of images, lets do a better job and create an Img component that's going to have three different states:

  1. default state just a plain old image
  2. interact as you mouse over you'll see the name of the plant
  3. favored image displays with a heart

The default state will just display the image, with alt tags etc. We will create a mouse over state that will display the name of the plant in an overlay. The final state will be a favorites state (we will toggle a heart on the image).

The idea is easy to grasp. The component has a value held within it. When the value changes, our component will automatically re-render. We can display different HTML depending on the internal value or state.

We can use HTML like events to capture interactions and trigger functions just like we can using plain old javascript and HTML. Those functions can then alter the internal state of the component, that will trigger a reactive re-render.

create the Img component

Start by creating the <Img image={image}/> component in a simple way that returns a valid <img> tag. Make a file called Img.js. Here is some starter code.

export const Img = props => {
    return (
        <img src={} alt={} />
    )
}

Test it out in your app. See if you can use this component in your Product component. You will have to import it, then iterate over the images array and pass in the data object via props. Look back over the code you have already written and duplicate the patterns that are already there.

useState

We are going to pull a utility function from the core React library called useState to create a reactive internal state variable called overlay. useState returns to us a reactive variable, and a special function to call to update that reactive variable.

import { useState } from "react"

export const Img = props => {
    const [overlay, setOverlay] = useState('default')
    return <img src={props.image.imageSrc} alt={props.image.title} />
}

Can you see how the reactive variable and the corresponding function to update it are destructured from the result of calling useState(). I am calling useState('') with an empty string '' that will be the initial starting value of the overlay reactive variable.

Stateful overlays

I'm imagining we can have different overlays over the image. We need to change up the HTML of the component to do this nicely. I'm going to wrap the image in a <figure> element and display the image with either or text and an emoji over the top of it. I'm just going to bake the styles into the HTML as they are structural and I want us to focus on using state in React. Later you can refactor this css and put it in your style sheet.

import { useState } from "react"

export const Img = props => {
    const [overlay, setOverlay] = useState('')
    const overlayStyles = {
        position: 'absolute',
        bottom: '.5rem',
        left: '.5rem',
        fontSize: '2rem'
    } 
    return (
        <figure style={{position: 'relative'}}>
            <img src={props.image.imageSrc} alt={props.image.title} />
            <figcaption style={overlayStyles}>{overlay}</figcaption>
        </figure>
    )
}

onClick

First lets toggle a heart. When the image is clicked you will add a heart, if you click it again it will be removed. We need to listen for mouse clicks, just like HTML we add an event listener and event handler. Add the event handler.

<figure style={{position: 'relative'}} onClick={() => setOverlay(overlay === '❤️' ? '' : '❤️')}>

All the HTML inline javascript functions like onclick, onmouseenter in React they are all CamelCased. onClick, onMouseEnter etc. All we are doing in our inline function is checking if overlay is a heart and un-setting it if it is and setting it if it is not. Its a toggle.

Now our component has 2 states.

3 States

There is more complexity when we add a third state. So lets refactor and add state for the heart and state for the plant name. We'll also add the mouse enter/leave- events with their own handlers.

import { useState } from "react"

export const Img = props => {
    const [title, setTitle] = useState('')
    const [heart, setHeart] = useState(true)
    
    const overlayStyles = {
        position: 'absolute',
        bottom: '.5rem',
        left: '.5rem',
        fontSize: '2rem'
    }

    const showTitle = () => {
        setTitle(props.image.title)
    }
    const hideTitle = () => {
        setTitle('')
    }
    return (
        <figure 
            style={{position: 'relative'}} 
            onClick={() => setHeart(!heart)}
            onMouseEnter={showTitle}
            onMouseLeave={hideTitle}
            >
            <img src={props.image.imageSrc} alt={props.image.title} />
            <figcaption style={overlayStyles}>{heart ? '❤️' : ''} {title}</figcaption>
        </figure>
    )
}

When you have lots of props that you are passing to a React component you'll often see them stacked up on their own line like I have done above in the <figure> element. It makes it easier to read.

Assignment

If there is no stock we should communicate this to our users. Can you update the image component to consider stock levels and visually indicate to your users if this item is available to buy or not.

hint we are not passing stock levels into our Img component at the moment.