npm install react@18.3.1 react-dom@18.3.1
quickstart % npm install react@18.3.1 react-dom@18.3.1
added 5 packages in 250ms
quickstart %
quickstart % npm install --save-dev @babel/core @babel/cli @babel/preset-react
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
added 89 packages, and audited 95 packages in 804ms
8 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
quickstart %
{
"presets": ["@babel/preset-react"]
}
quickstart % cat .babelrc
{
"presets": ["@babel/preset-react"]
}
quickstart %
<html>
<body>
Live Server is ready.
</body>
</html>
quickstart % unzip ~/Downloads/f165_static_application_files.zip
Archive: /___/_______/Downloads/f165_static_application_files.zip
Implementation by Anton Scheffer
extracting: icons/app-icon-144-rounded.png
inflating: icons/app-icon-192.png
inflating: icons/app-icon-256-rounded.png
extracting: icons/app-icon-32.png
inflating: icons/app-icon-512.png
quickstart %
<script type="importmap">
{
"imports": {
"react": "https://cdn.jsdelivr.net/npm/react@18.3.1/+esm",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@18.3.1/+esm"
}
}
</script>
import React from 'react'; | |
function MyButton() { | |
return ( | |
<button> | |
I'm a button | |
</button> | |
); | |
} | |
export default function App() { | |
return ( | |
<div> | |
<h1>Welcome to my app</h1> | |
<MyButton /> | |
</div> | |
); | |
} |
quickstart % npx babel App.jsx --out-file App.js
quickstart %
作成済みのリージョンMyButtonをコメント・アウトし、新たにリージョンAppを作成して、HTMLコードに以下を記述します。
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import App from "#APP_FILES#App.js";
/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <App /> );
</script>
import React from 'react'; | |
const user = { | |
name: 'Hedy Lamarr', | |
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg', | |
imageSize: 90, | |
}; | |
export default function Profile() { | |
return ( | |
<> | |
<h1>{user.name}</h1> | |
<img | |
className="avatar" | |
src={user.imageUrl} | |
alt={'Photo of ' + user.name} | |
style={{ | |
width: user.imageSize, | |
height: user.imageSize | |
}} | |
/> | |
</> | |
); | |
} |
npx babel Profile.jsx --out-file Profile.js
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import Profile from "#APP_FILES#Profile.js";
/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <Profile /> );
</script>
@scope(#container) {
.avatar {
border-radius: 50%;
}
}
import React from 'react'; | |
const products = [ | |
{ title: 'Cabbage', isFruit: false, id: 1 }, | |
{ title: 'Garlic', isFruit: false, id: 2 }, | |
{ title: 'Apple', isFruit: true, id: 3 }, | |
]; | |
export default function ShoppingList() { | |
const listItems = products.map(product => | |
<li | |
key={product.id} | |
style={{ | |
color: product.isFruit ? 'magenta' : 'darkgreen' | |
}} | |
> | |
{product.title} | |
</li> | |
); | |
return ( | |
<ul>{listItems}</ul> | |
); | |
} |
npx babel ShoppingList.jsx --out-file ShoppingList.js
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import ShoppingList from "#APP_FILES#ShoppingList.js";
/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <ShoppingList /> );
</script>
import React, { useState } from 'react'; | |
export default function MyApp() { | |
return ( | |
<div> | |
<h1>Counters that update separately</h1> | |
<MyButton /> | |
<MyButton /> | |
</div> | |
); | |
} | |
function MyButton() { | |
const [count, setCount] = useState(0); | |
function handleClick() { | |
setCount(count + 1); | |
} | |
return ( | |
<button onClick={handleClick}> | |
Clicked {count} times | |
</button> | |
); | |
} |
npx babel MyApp.jsx --out-file MyApp.js
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import MyApp from "#APP_FILES#MyApp.js";
/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <MyApp /> );
</script>
/* | |
* Reactのカスタム要素に含まれるボタンをクリックしたときに、 | |
* APEXがページを送信するのを抑制する。 | |
*/ | |
document.addEventListener('click', (event) => { | |
const buttons = document.querySelectorAll("#container button"); | |
buttons.forEach( (button) => { | |
if (button === event.target) { | |
console.log('click on button within React component is ignored to prevent page submittion.', event.target); | |
event.preventDefault(); | |
} | |
}); | |
}); |
import React, { useState } from 'react'; | |
export default function MyApp() { | |
const [count, setCount] = useState(0); | |
function handleClick() { | |
setCount(count + 1); | |
} | |
return ( | |
<div> | |
<h1>Counters that update together</h1> | |
<MyButton count={count} onClick={handleClick} /> | |
<MyButton count={count} onClick={handleClick} /> | |
</div> | |
); | |
} | |
function MyButton({ count, onClick }) { | |
return ( | |
<button onClick={onClick}> | |
Clicked {count} times | |
</button> | |
); | |
} |
npx babel MyApp2.jsx --out-file MyApp2.js
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import MyApp from "#APP_FILES#MyApp2.js";
/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <MyApp /> );
</script>
import React, { useState } from 'react'; | |
function Square({ value, onSquareClick }) { | |
return ( | |
<button className="square" onClick={onSquareClick}> | |
{value} | |
</button> | |
); | |
} | |
function Board({ xIsNext, squares, onPlay }) { | |
function handleClick(i) { | |
if (calculateWinner(squares) || squares[i]) { | |
return; | |
} | |
const nextSquares = squares.slice(); | |
if (xIsNext) { | |
nextSquares[i] = 'X'; | |
} else { | |
nextSquares[i] = 'O'; | |
} | |
onPlay(nextSquares); | |
} | |
const winner = calculateWinner(squares); | |
let status; | |
if (winner) { | |
status = 'Winner: ' + winner; | |
} else { | |
status = 'Next player: ' + (xIsNext ? 'X' : 'O'); | |
} | |
return ( | |
<> | |
<div className="status">{status}</div> | |
<div className="board-row"> | |
<Square value={squares[0]} onSquareClick={() => handleClick(0)} /> | |
<Square value={squares[1]} onSquareClick={() => handleClick(1)} /> | |
<Square value={squares[2]} onSquareClick={() => handleClick(2)} /> | |
</div> | |
<div className="board-row"> | |
<Square value={squares[3]} onSquareClick={() => handleClick(3)} /> | |
<Square value={squares[4]} onSquareClick={() => handleClick(4)} /> | |
<Square value={squares[5]} onSquareClick={() => handleClick(5)} /> | |
</div> | |
<div className="board-row"> | |
<Square value={squares[6]} onSquareClick={() => handleClick(6)} /> | |
<Square value={squares[7]} onSquareClick={() => handleClick(7)} /> | |
<Square value={squares[8]} onSquareClick={() => handleClick(8)} /> | |
</div> | |
</> | |
); | |
} | |
export default function Game() { | |
const [history, setHistory] = useState([Array(9).fill(null)]); | |
const [currentMove, setCurrentMove] = useState(0); | |
const xIsNext = currentMove % 2 === 0; | |
const currentSquares = history[currentMove]; | |
function handlePlay(nextSquares) { | |
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; | |
setHistory(nextHistory); | |
setCurrentMove(nextHistory.length - 1); | |
} | |
function jumpTo(nextMove) { | |
setCurrentMove(nextMove); | |
} | |
const moves = history.map((squares, move) => { | |
let description; | |
if (move > 0) { | |
description = 'Go to move #' + move; | |
} else { | |
description = 'Go to game start'; | |
} | |
return ( | |
<li key={move}> | |
<button onClick={() => jumpTo(move)}>{description}</button> | |
</li> | |
); | |
}); | |
return ( | |
<div className="game"> | |
<div className="game-board"> | |
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> | |
</div> | |
<div className="game-info"> | |
<ol>{moves}</ol> | |
</div> | |
</div> | |
); | |
} | |
function calculateWinner(squares) { | |
const lines = [ | |
[0, 1, 2], | |
[3, 4, 5], | |
[6, 7, 8], | |
[0, 3, 6], | |
[1, 4, 7], | |
[2, 5, 8], | |
[0, 4, 8], | |
[2, 4, 6], | |
]; | |
for (let i = 0; i < lines.length; i++) { | |
const [a, b, c] = lines[i]; | |
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { | |
return squares[a]; | |
} | |
} | |
return null; | |
} |
npx babel Game.jsx --out-file Game.js
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import Game from "#APP_FILES#Game.js";
/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <Game /> );
</script>
/* Game */ | |
@scope(#container) { | |
* { | |
box-sizing: border-box; | |
} | |
body { | |
font-family: sans-serif; | |
margin: 20px; | |
padding: 0; | |
} | |
h1 { | |
margin-top: 0; | |
font-size: 22px; | |
} | |
h2 { | |
margin-top: 0; | |
font-size: 20px; | |
} | |
h3 { | |
margin-top: 0; | |
font-size: 18px; | |
} | |
h4 { | |
margin-top: 0; | |
font-size: 16px; | |
} | |
h5 { | |
margin-top: 0; | |
font-size: 14px; | |
} | |
h6 { | |
margin-top: 0; | |
font-size: 12px; | |
} | |
code { | |
font-size: 1.2em; | |
} | |
ul { | |
padding-inline-start: 20px; | |
} | |
.square { | |
background: #fff; | |
border: 1px solid #999; | |
float: left; | |
font-size: 24px; | |
font-weight: bold; | |
line-height: 34px; | |
height: 34px; | |
margin-right: -1px; | |
margin-top: -1px; | |
padding: 0; | |
text-align: center; | |
width: 34px; | |
} | |
.board-row:after { | |
clear: both; | |
content: ''; | |
display: table; | |
} | |
.status { | |
margin-bottom: 10px; | |
} | |
.game { | |
display: flex; | |
flex-direction: row; | |
} | |
.game-info { | |
margin-left: 20px; | |
} | |
} |
import React, { useState } from 'react'; | |
function FilterableProductTable({ products }) { | |
const [filterText, setFilterText] = useState(''); | |
const [inStockOnly, setInStockOnly] = useState(false); | |
return ( | |
<div> | |
<SearchBar | |
filterText={filterText} | |
inStockOnly={inStockOnly} | |
onFilterTextChange={setFilterText} | |
onInStockOnlyChange={setInStockOnly} /> | |
<ProductTable | |
products={products} | |
filterText={filterText} | |
inStockOnly={inStockOnly} /> | |
</div> | |
); | |
} | |
function ProductCategoryRow({ category }) { | |
return ( | |
<tr> | |
<th colSpan="2"> | |
{category} | |
</th> | |
</tr> | |
); | |
} | |
function ProductRow({ product }) { | |
const name = product.stocked ? product.name : | |
<span style={{ color: 'red' }}> | |
{product.name} | |
</span>; | |
return ( | |
<tr> | |
<td>{name}</td> | |
<td>{product.price}</td> | |
</tr> | |
); | |
} | |
function ProductTable({ products, filterText, inStockOnly }) { | |
const rows = []; | |
let lastCategory = null; | |
products.forEach((product) => { | |
if ( | |
product.name.toLowerCase().indexOf( | |
filterText.toLowerCase() | |
) === -1 | |
) { | |
return; | |
} | |
if (inStockOnly && !product.stocked) { | |
return; | |
} | |
if (product.category !== lastCategory) { | |
rows.push( | |
<ProductCategoryRow | |
category={product.category} | |
key={product.category} /> | |
); | |
} | |
rows.push( | |
<ProductRow | |
product={product} | |
key={product.name} /> | |
); | |
lastCategory = product.category; | |
}); | |
return ( | |
<table> | |
<thead> | |
<tr> | |
<th>Name</th> | |
<th>Price</th> | |
</tr> | |
</thead> | |
<tbody>{rows}</tbody> | |
</table> | |
); | |
} | |
function SearchBar({ | |
filterText, | |
inStockOnly, | |
onFilterTextChange, | |
onInStockOnlyChange | |
}) { | |
return ( | |
<form> | |
<input | |
type="text" | |
value={filterText} placeholder="Search..." | |
onChange={(e) => onFilterTextChange(e.target.value)} /> | |
<label> | |
<input | |
type="checkbox" | |
checked={inStockOnly} | |
onChange={(e) => onInStockOnlyChange(e.target.checked)} /> | |
{' '} | |
Only show products in stock | |
</label> | |
</form> | |
); | |
} | |
const PRODUCTS = [ | |
{category: "Fruits", price: "$1", stocked: true, name: "Apple"}, | |
{category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"}, | |
{category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"}, | |
{category: "Vegetables", price: "$2", stocked: true, name: "Spinach"}, | |
{category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"}, | |
{category: "Vegetables", price: "$1", stocked: true, name: "Peas"} | |
]; | |
export default function App() { | |
return <FilterableProductTable products={PRODUCTS} />; | |
} |
npx babel App3.jsx --out-file App3.js
<div id="container"></div>
<script type="text/babel" data-presets="react" data-type="module">
import React from 'react';
import ReactDOM from 'react-dom';
import App from '#APP_FILES#App3.js';
/* render React component within an APEX region */
const container = document.getElementById("container");
const root = ReactDOM.createRoot( container );
root.render( <App /> );
</script>