<info 340/>

Interactive React Catchup

Tim Carlson
Autumn 2024

View of the Day

  • Create and Cleanup New Vite React Project

  • Conditional Formatting

  • React Interdependent Interactions 

  • Managing State

  • Composition

  • Controlled Form

  • External Library

  • Multiple Views of Data

 

Project Draft 2

What we are looking for: Refactored Draft 1 into a React App 

Converted the HTML/CSS from draft 1 into a published React app. Began to add interactive functionality.

  • ALL of content rendered: You should have Components that render DOM for every part of your page
  • Has Components w/ props and data: Organize your Components! Can hard-code sample data for now
  • Uses routes to show all page content ("next" week)
  • Has 1 feature implemented: Includes event handling and state manipulation (today). Can be the small feature!
  • Fixes issues from draft 1: You're revising the HTML/CSS, fix problems while you're at it!
  • Published to Firebase hosting: get that working this draft
    (see assignment for details; "next" week)
# switch to starter branch to get new starter code
git checkout starter

# download new starter code
git pull

# switch back to main branch for coding
git checkout main

# merge in new starter code (use default msg)
git merge starter --no-edit

# code and enjoy!

Get the starter code from the starter branch, but do all of your work on main.

Updating Lecture Code

Arcade App

Sample data: (fecgames.json)

[
  {
    "ImagePath": "img/AirFX.jpg",
    "Manufacturer": "ICE Games",
    "GameName": "Air FX Hockey Table",
    "GameDescription": "High Falutin' Air Hockey. Fancy colors, Fancy paddles",
    "ManufacturerSite": "https://www.icegame.com/"
  },
  {
    "ImagePath": "img/CruisnBlast.jpg",
    "Manufacturer": "Raw Thrills",
    "GameName": "Cruis'n Blast",
    "GameDescription": "The only place where speeding is encouraged ...",
    "ManufacturerSite": "https://rawthrills.com/games/cruisn-blast/"
  },
  {
    "ImagePath": "img/HaloFireTeamRaven.jpg",
    "Manufacturer": "Raw Thrills",
    "GameName": "Halo FireTeam Raven",
    "GameDescription": "Suit up, Spartan! There’s no respawn in the arcade ...",
    "ManufacturerSite": "https://rawthrills.com/games/halo-fireteam-raven/"

  },
  {
    "ImagePath": "img/HungryHippos.jpg",
    "Manufacturer": "Adrenaline Arcade",
    "GameName": "Hungry, Hungry Hippos",
    "GameDescription": "Bigger than the board game! Feed those hungry hippos ...",
    "ManufacturerSite": "https://adrenalinearcade.com/hungry-hungry-hippos-v2/"

  }
  ]

Demo overview: Components

Vite Dev Server

Text

Select "React"

Select "JavaScript"

Cleanup Project folder (how i do it)

  • npm install
  • npm run dev
  • Create a src/components folder for all components you create
  • Move the App.jsx into the src/components folder 
  • Fix reference to App in main.jsx
  • Delete the app.css file (we use 1 css file)
  • Cleanup index.html (include meta elements that are missing)
  • Figure where you want to keep static "build time" elements
    src/assets is fine if you want
  • Add folder for 'runtime' assets for testing (like images.
    I use 'public/img' for example

React Libraries

React components are structured to be self-contained, re-usable elements... so there are lots of pre-defined components online you can use!

In order to use a component in your app:

  1. Find it! (via npm, google, etc). Read the documentation
  2. Install it! npm install lib-name
  3. Import it! import { ComponentName } from 'lib-name'
    • (import structure may vary per library)
  4. Render it! <ComponentName />
    • Remember to pass any expected props!

React Bootstrap (external library)

https://react-bootstrap.netlify.app/

React Events

let currentCount = 0;
  
  const handleClick = (event) => {
    console.log("you clicked me!");
    currentCount = currentCount + 1;
  }
 

This local variable (currentCount) won't work because it doesn't cause the component to re-render

Also can't store it in a prop because they are read only (next slide)

React Events

We add user interaction in React the same way as with the DOM: by listening for events and executing callback functions when they occur. 

function MyButton() {
  //A function that will be called when clicked 
  //The name is conventional, but arbitrary.
  //The callback will be passed the DOM event as usual
  const handleClick = function(event) {
    console.log("clicky clicky");
  }

  //make a button with an `onClick` attribute!
  //this "registers" the listener and sets the callback
  return <button onClick={handleClick}>Click me!</button>;
}

special React prop

can only put listeners on HTML
elements, not Components!

function MyButton(props) {
  //A function that will be called when clicked 
  //The name is conventional, but arbitrary.
  //The callback will be passed the DOM event as usual
  const handleClick = (event) => {
    console.log("clicky clicky");
  }

  //make a button with an `onClick` attribute!
  //this "registers" the listener and sets the callback
  return <button onClick={handleClick}>Click me!</button>;
}

Composition 

  • Keep your state data as data (not html elements)
  • Don't duplicate state 
  • Your "other  logic" section of the code will transform your state data to html elements when it is rerendered
  • Initially (before firebase) the app will not be persistant. 
  • With firebase, you will attach a listener to the fb database and will refresh your data when fb tells you there has been an update
  • Learn how the map function works!

In addition to the props, React components can also track their internal state. This keeps track of information about the Component that may change due to user interaction.

React State

State is reserved only for interactivity, that is, data that changes over time

Some examples of state data:

  1. The sorted order of child components
  2. Timers or dynamic content
  3. Which model data are shown!

State

  • When two siblings need to share info the state variable goes up to the parent along with the 'setter' for that variable
  • The state data and the 'setter' can be passed to the siblings as props from the parent

Game State in this example

App

< FECGames FECGameList={gameArray} />

CreateOrEditGame

<CreateOrEditGame addNewGameCallBack={addGame}  /> 

function App() {
  const [gameArray, setGameArray] = useState(FECGameList)
  const addGame = (gameobj) => {
	  const updateGameArray = [...gameArray, gameobj];
    	setGameArray(updateGameArray); //update state and re-render
  }
    return (
    <div className='container'>
      <div className="App">
          <NavBar />
          <FECGames FECGameList={gameArray} />
          <CreateOrEditGame addNewGameCallback={addGame}/>
...

FECGames

Composition

import React, { useState } from 'react';
import NavBar from './NavBar';
import { FECGames } from './FECGames.jsx';
import { CreateOrEditGame } from './CreateOrEditGame.jsx'

import FECGameList from '../data/FECGameList.json';

function App() {

  const [gameArray, setGameArray] = useState(FECGameList)

  const addGame = (gameobj) => {

    const updateGameArray = [...gameArray, gameobj];
    setGameArray(updateGameArray); //update state and re-render
  }
 
  return (
    <div className='container'>
      <div className="App">
          <NavBar />
          <FECGames FECGameList={gameArray} />
          <CreateOrEditGame addNewGameCallback={addGame}/>
      </div>
    </div>
  );
}

export default App;

'Other logic' in FECGames

	//imports
export function FECGames(props) {

    
    // State variables 
...
	// Event Handlers
...	
    // Other logic (filtering, sorting, etc) to get our data in the correct order and filtered
    
    const filteredFECGames = props.FECGameList.filter(filterBy);

    // Then sort the filtered list of game cards based alphabetical order
    const FilteredSortedFECGames = filteredFECGames.sort((g1, g2) => {
        if (g1.GameName < g2.GameName) { return -1 }
        if (g1.GameName > g2.GameName) { return 1 }
        return 0;
    });


    // This creates the array of filter options for Games in the drop down
    const FECGamesFilterOptions = FilteredSortedFECGames.map((gameObj) => {
        const { GameName } = gameObj;
        const optionElement = <Dropdown.Item as="button" value={GameName} key={GameName} onClick={handleGameFilterClick}>{GameName}</Dropdown.Item>
        return optionElement
    });
    // This pre-appends "All" at the top of the list of filter options for Games
    FECGamesFilterOptions.unshift(<Dropdown.Item as="button" value="All" key="All" onClick={handleGameFilterClick}>All Games </Dropdown.Item>)

...

'Other logic' in FECGames

   ...
    function FECGameList(props) {


            const cardList = FilteredSortedFECGames.map((cardObj) => {
                const cardItem = FECGameCard(cardObj);
                return cardItem;
            })

            return (
                <div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4" >
                    {cardList}
                </div>
            )

        }


    return (
        <div>
            <FilterSet />
            <FECGameList />
        </div>

    );

}

transform happens outside of return

Interdependent Interactions

   export function FECGames(props) {

    // State variables for the two filters (Games and Manufacturers)
    const [gameFilter, setGameFilter] = useState("All")
    const [manufacturerFilter, setManufacturerFilter] = useState("All")
    const [viewToggleValue, setViewToggleValue] = useState("cards")

    // handlers for when one of the filter options is selected in the filter dropdowns
    const handleGameManufacturerFilterClick = (event) => {
        // console.log("changed the filter", event);
        // console.log("event.target.value :", event.target.value);
        setManufacturerFilter(event.target.value);
    }

    const handleGameFilterClick = (event) => {
        // console.log("changed the Game filter", event);
        // console.log("event.target.value :", event.target.value);
        setGameFilter(event.target.value);
    }

    const handleToggleViewChange = (event) => {
        setViewToggleValue(event.target.value)
        console.log(event.target.value)

    }

Each interaction has its own state var and handler

React Form Inputs

To access the value in an <input>, save that value in the state (and update it onChange). This is called a controlled form.

use DOM event to refer to which input element

function MyInput(props) {
  const [inputValue, setInputValue] = useState('')
  
  const handleChange = (event) => {
    let newValue = event.target.value
    setInputValue(newValue);
  }

  return (
    <div>
      <input type="text" onChange={handleChange} value={inputValue} />
      You typed: {value}
    </div>);
  )
}

Controlled Form

   export function CreateOrEditGame(props) {

    const { addNewGameCallback } = props;
    
    const [gameNameTextValue, setGameNameText] = useState("");
...

    const handleGameNameTextChange = (event) => {
        const inputtedGameNameValue = event.target.value;
        setGameNameText(inputtedGameNameValue);
        
    }
...

    const handleSubmit = (event) => {
        event.preventDefault();

        const gameObj = {
            GameName: gameNameTextValue,
            Manufacturer: manufacturerNameTextValue,
            ImagePath: imgPathTextValue,
            ManufacturerSite: manufacturerURLTextValue
        }

        addNewGameCallback(gameObj)
...
}

continued on next slide

Controlled Form (pt 2)

  ... 
      return (
        <div className="container">
            
            <Form className="bg-light border border-primary" onSubmit={handleSubmit}>
            <p className="m-1 formtitle">New Game Form</p>
                <Form.Group className="mb-3" controlId="formGameName">
                    <Form.Label className="m-1">Game Name</Form.Label>
                    <Form.Control
                        type="text"
                        placeholder="Enter Game Name"
                        value={gameNameTextValue}
                        onChange={handleGameNameTextChange}
                    />
                    <Form.Text className="text-muted m-1" >
                        Examples: "Twister", "Typhoon", "Space Invaders"
                    </Form.Text>
                </Form.Group>

...
                <Button variant="primary" type="submit">
                    Submit
                </Button>
            </Form>
        </div>
    );
}

Conditional Rendering

You can use control logic (if statements) to specify whether or not a component should be rendered.

function ConditionalPanel(props) {
  //assign element to show to variable
  let thingToRender = null; //null element will not render
  if(conditionOne){ //based on props or state
    thingToRender = <OptionA />
  } else if(conditionTwo) {
    thingToRender = <OptionB />
  } else if(conditionThree) {
    return null; //show nothing!
  }
    
  //keep return statement as simple as possible!
  return (<div>{thingToRender}</div>);
}
function ConditionPanel(props) {
  //can use inline expressions via shortcutting. Use with caution
  return (
    <div>
      {conditionOne == true && <OptionA />}
    </div>
  )
}

Conditional Formatting

    function FECGameList(props) {
        if (viewToggleValue === "cards") {
            const cardList = FilteredSortedFECGames.map((cardObj) => {
                const cardItem = FECGameCard(cardObj);
                return cardItem;
            })

            return (
                <div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4" >
                    {cardList}
                </div>
            )
        }

        else {
            const rowList = FilteredSortedFECGames.map((cardObj) => {
                const rowItem = FECGameRow(cardObj);
                return rowItem;
            })
            return (
                <Table striped bordered hover size="sm">
                    <thead>
                        <tr>
                            <th>img</th>
 ...
                        </tr>
                    </thead>
                    <tbody>
                        {rowList}
                    </tbody>
                </Table>
            )
        }
    }

transform to cards

transform to table

Conditional Rendering

export function ChatPane(props) {
...

  const messageItemArray = channelMessages.map((messageObj) => {
    const element = (
      <MessageItem 
        messageData={messageObj} 
        key={messageObj.timestamp} 
      />
    )
    return element;
  })

  if (channelMessages.length === 0) {
    return <p>No Messages Yet</p>
  }

   return (
    <div className="scrollable-pane">
      <div className="pt-2 my-2">
        {currentChannel === 'general' &&
          <div>
            <button className="btn btn-outline-primary mb-3" onClick={handleClick}>
              Click me: {currentCount}
            </button>
            <hr />
          </div>}
        {messageItemArray}
      </div>
    </div>
  )

If there are no messages, return "No Messages Yet" rather than the empty array below

That weird conditional syntax from slide 13

info340au24-interactive-react-catchup

By Tim Carlson

info340au24-interactive-react-catchup

interactive react

  • 288