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
StoreList : Renders the list of stores
- StoreList has input field to add new store
- Needs StoreList (data)
- Needs Current Selected Store (data)
NewFormItem: adds new items with correct store
- Needs current Selected Store for dropdown (data)
StoreList needs the Store List data array. NewFormItem needs access to StoreList data array to fill the dropdown.
Elevate Store List data array to the common parent (App)
ShoppingList needs the Shopping List data array. NewFormItem needs access to update Shopping List data array
Elevate the Shopping List data array and the update method to the common parent (App)
ShoppingList needs the currently selected store. StoreList sets the currently selected store.
Elevate currently selected store and update method to common parent (App)
If multiple components rely on the same
data (variable), you should "lift up" that
state
to a shared parent, who can pass the information back down as
props
.
ChildA
ChildB
Parent
Has Data
(state)
Needs Data
<ChildA data={data} />
Has Data (prop)
Has Data (prop)
<ChildA data={data} />
Has Data (state)
StoreList
App
ShoppingList
export default function App() {
const items = [{ timestamp: 1, store: 'QFC', name: 'Milk' }, { timestamp: 2, store: 'QFC', name: 'Bread' },
{ timestamp: 3, store: 'QFC', name: 'Eggs' }, { timestamp: 4, store: 'Target', name: 'Throw Rug' },
{ timestamp: 5, store: 'Walmart', name: 'Duct Tape' }];
const [itemList, setItemList] = useState(items);
function addItem (item) {
const timestamp = Date.now();
item = {timestamp, store: currentStore, name: item};
setItemList([...itemList, item]);
};
...
<StoreList
stores={storeList}
addStore={addStore}
currentStore={currentStore}
changeCurrentStore={changeCurrentStore}
/>
<ShoppingList itemList={itemList} />
<NewItemForm stores={storeList} addItem={addItem} />
export default function App() {
const items = [{ timestamp: 1, store: 'QFC', name: 'Milk' }, { timestamp: 2, ...];
const [itemList, setItemList] = useState(items);
function addItem (item) {
const timestamp = Date.now();
item = {timestamp, store: currentStore, name: item};
setItemList([...itemList, item]);
};
...
return (
...
<div >
<NewItemForm stores={storeList} addItem={addItem} className="mt-auto" />
</div>
...
);
exportfunction NewItemForm(props) {
...
const handleClick = (event) => { props.addItem(item);}
return (
...
<form>
<button type="button" onClick={handleClick}>
Add
</button>
</form>
... );
export default function App() {
const stores = [
{ timestamp: 1, name: 'Amazon' },{ timestamp: 2, name: 'Walmart' },... ];
const [storeList, setStoreList] = useState(stores); // Default store
function addStore (store) {
const timestamp = Date.now();
store = {timestamp, name: store};
setStoreList([...storeList, store]);
}
...
return (
...
<div>
<StoreList stores={storeList} addStore={addStore} currentStore={currentStore}
changeCurrentStore={changeCurrentStore} />
</div>
... );
exportfunction StoreList(props) {
const [newStore, setNewStore] = useState("");
const addNewStore = (event) => {
if (newStore.trim() !== "") {
props.addStore(newStore); // Add new item to list
setNewStore(""); // Clear input field after adding }};
...
return (
...
<input type="text" value={newStore} onChange={handleInputChange}
placeholder="New store name" />
<button onClick={addNewStore}>Add Store</button>
... );
export default function App() {
const [currentStore, setCurrentStore] = useState('QFC'); // Default store
function changeCurrentStore (store) {
setCurrentStore(store);
}
...
return (
...
<div className=" h-100">
<StoreList stores={storeList} addStore={addStore}
currentStore={currentStore} changeCurrentStore={changeCurrentStore} />
</div>
... );
exportfunction StoreList(props) {
const { stores, currentStore} = props;
function handleCurrentStoreClick(event) {
props.changeCurrentStore(event.target.innerText);
}
const storeList = stores.map((store) => {
let classList = "list-group-item bg-secondary text-light"; // Apply Bootstrap styles
if (store.name === currentStore) {
classList = "list-group-item text-white bg-dark";
}
return <li key={store.timestamp} className={classList} onClick={handleCurrentStoreClick}>{store.name}</li>;
});
..
return (
...
... );
Complete task list for Week 7 (all items!)
Do Problem Set 07 -- it's how you learn this!
Review Ch 16-17: React & Interactive React
Problem Set 07 due Sunday (moved!)
Project Draft 2 due next week
Next time: Router (multiple pages & navigation!)
By Tim Carlson
interactive react