Tim Carlson
I'm a lecturer at University of Washington
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.
# 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.
[
{
"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
Text
Select "React"
Select "JavaScript"
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:
npm install lib-name
import { ComponentName } from 'lib-name'
<ComponentName />
https://react-bootstrap.netlify.app/
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)
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>;
}
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.
State is reserved only for interactivity, that is, data that changes over time
Some examples of state data:
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
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;
//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>)
...
...
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
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
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>);
)
}
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
...
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>
);
}
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>
)
}
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
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
By Tim Carlson
interactive react