react: GlobalState: data, appCtx + polling feature
- Polling
 - time beased URI polling
 - can be diabled
- GlobaState
 - utilizes Flux pattern
 - data:
    for storing data for UI to work with
 - appData:
    for string global UI state
- major refactoring
			
			
This commit is contained in:
		
							parent
							
								
									fa29d2a631
								
							
						
					
					
						commit
						efd7127575
					
				@ -16,3 +16,7 @@
 | 
				
			|||||||
.App-link {
 | 
					.App-link {
 | 
				
			||||||
  color: #61dafb;
 | 
					  color: #61dafb;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.Container {
 | 
				
			||||||
 | 
					  margin-top: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,47 +1,31 @@
 | 
				
			|||||||
import './App.css';
 | 
					import './App.css';
 | 
				
			||||||
import React, { useState, useEffect, useCallback } from 'react';
 | 
					import React from 'react'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  BrowserRouter,
 | 
					  BrowserRouter,
 | 
				
			||||||
  Routes,
 | 
					  Routes,
 | 
				
			||||||
  Route,
 | 
					  Route,
 | 
				
			||||||
} from "react-router-dom";
 | 
					} from "react-router-dom"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Header from "./Header"
 | 
					import Header from "./components/Header"
 | 
				
			||||||
import Leaderboard from "./Leaderboard";
 | 
					import Leaderboard from "./components/Leaderboard"
 | 
				
			||||||
import GameProposal from "./GameProposal";
 | 
					import GameProposal from "./components/GameProposal"
 | 
				
			||||||
 | 
					import DataPolling from './components/DataPolling';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//import { UserProvider } from "../contexts/UserProvider"
 | 
				
			||||||
 | 
					//import { GameProposalProvider } from './context/GameProposal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function App() { 
 | 
					function App() { 
 | 
				
			||||||
  const [games, setGames] = useState(null);
 | 
					 | 
				
			||||||
  const [polling, setPolling] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const pollGames = useCallback(() => {     
 | 
					 | 
				
			||||||
    if (polling)
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setPolling(true);
 | 
					 | 
				
			||||||
    fetch('/api/gamestate')
 | 
					 | 
				
			||||||
      .then(response => response.json())
 | 
					 | 
				
			||||||
      .then(data => {
 | 
					 | 
				
			||||||
        setGames(data);
 | 
					 | 
				
			||||||
        setPolling(false);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(err => console.log(err.message));
 | 
					 | 
				
			||||||
  }, [polling]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    const timer = setInterval(pollGames(), 35 * 1000); // <<-- poll new gamestates every 35 sec
 | 
					 | 
				
			||||||
    return clearInterval(timer);
 | 
					 | 
				
			||||||
  }, [pollGames])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <div className="App">
 | 
					  return <div className="App">
 | 
				
			||||||
    <BrowserRouter>
 | 
					    <BrowserRouter>
 | 
				
			||||||
      <Header/>
 | 
					        <Header/>
 | 
				
			||||||
      <div className="Container">
 | 
					        <DataPolling/>
 | 
				
			||||||
        <Routes>
 | 
					        <div className="Container">
 | 
				
			||||||
          <Route path="/leaderboard" element={<Leaderboard/>} />
 | 
					          <Routes>
 | 
				
			||||||
          <Route path="/gameproposal" element={<GameProposal games={games}/>} />
 | 
					            <Route path="/leaderboard" element={<Leaderboard/>} />
 | 
				
			||||||
        </Routes>
 | 
					              <Route path="/gameproposal" element={<GameProposal/>} />
 | 
				
			||||||
      </div>
 | 
					          </Routes>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
    </BrowserRouter>
 | 
					    </BrowserRouter>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +0,0 @@
 | 
				
			|||||||
import { Link } from "react-router-dom";
 | 
					 | 
				
			||||||
import './Header.css';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function Header() {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <p>
 | 
					 | 
				
			||||||
        <h1>CordaCheckers</h1>
 | 
					 | 
				
			||||||
        <nav>
 | 
					 | 
				
			||||||
          <Link to="/leaderboard">Leaderboard</Link> {"| "}
 | 
					 | 
				
			||||||
          <Link to="/gameproposal">Game Proposal</Link> {"| "}
 | 
					 | 
				
			||||||
          <Link to="/game">Active Games</Link> {"| "}
 | 
					 | 
				
			||||||
          <Link to="/archive">Archive</Link> {"| "}
 | 
					 | 
				
			||||||
          <Link to="about">About</Link>
 | 
					 | 
				
			||||||
        </nav>
 | 
					 | 
				
			||||||
      </p>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
							
								
								
									
										16
									
								
								webapp/src/components/DataPolling/index.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								webapp/src/components/DataPolling/index.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import React from "react"
 | 
				
			||||||
 | 
					import { AppData } from "../../context/data"
 | 
				
			||||||
 | 
					import { AppContext } from "../../context/app"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function DataPolling() {
 | 
				
			||||||
 | 
					    const [appData] = React.useContext(AppData)
 | 
				
			||||||
 | 
					    const [appCtx, dispatchAppData] = React.useContext(AppContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <div className={DataPolling.name}>
 | 
				
			||||||
 | 
					        polling
 | 
				
			||||||
 | 
					        <button onClick={() => dispatchAppData({type: "togglePolling"})}>
 | 
				
			||||||
 | 
					            { appCtx.disablePolling === true ? "off" : "on" }
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					        { appData.fetching }
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -6,11 +6,11 @@ export function Accept({uuid}) {
 | 
				
			|||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Reject({uuid}) {
 | 
					// export function Reject({uuid}) {
 | 
				
			||||||
    return <button className="action" id={uuid} type="submit">
 | 
					//     return <button className="action" id={uuid} type="submit" >
 | 
				
			||||||
        Reject
 | 
					//         Reject
 | 
				
			||||||
        </button>
 | 
					//         </button>
 | 
				
			||||||
}
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Cancel({uuid}) {
 | 
					export function Cancel({uuid}) {
 | 
				
			||||||
    return <button className="action" id={uuid} type="submit">
 | 
					    return <button className="action" id={uuid} type="submit">
 | 
				
			||||||
							
								
								
									
										24
									
								
								webapp/src/components/GameProposal/GameProposalCancel.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								webapp/src/components/GameProposal/GameProposalCancel.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import React, {useState} from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Cancel({uuid}) {
 | 
				
			||||||
 | 
					    const [pending, setPending] = useState(new Map())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const [key, value] of pending)
 | 
				
			||||||
 | 
					        console.log("cancel", key, value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function sendRequest(uuid2reject) {
 | 
				
			||||||
 | 
					        const nextPending = new Map(pending)
 | 
				
			||||||
 | 
					        nextPending.set(uuid2reject, null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setPending(nextPending)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const status = pending.get(uuid)
 | 
				
			||||||
 | 
					    const isPending = status !== undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <button 
 | 
				
			||||||
 | 
					        className={isPending ? "visible" : "action"} 
 | 
				
			||||||
 | 
					        onClick={() => sendRequest(uuid) }>
 | 
				
			||||||
 | 
					            Cancel
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								webapp/src/components/GameProposal/Reject.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								webapp/src/components/GameProposal/Reject.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import React, {useState} from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Reject({uuid}) {
 | 
				
			||||||
 | 
					    const [pending, setPending] = useState([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const [key, value] of Object.entries(pending))
 | 
				
			||||||
 | 
					        console.log("pending ", key, value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function sendRequest(reject_uuid) {
 | 
				
			||||||
 | 
					        setPending(             // Replace the old array
 | 
				
			||||||
 | 
					            [                   // with a new array consisting of:
 | 
				
			||||||
 | 
					                ...pending,     // - all the old items
 | 
				
			||||||
 | 
					                { uuid: reject_uuid }  // - and a new item at the end
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <button className="action" onClick={() => sendRequest(uuid) }>
 | 
				
			||||||
 | 
					        Reject
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,24 +1,25 @@
 | 
				
			|||||||
 | 
					import './index.css';
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import {Accept, Reject, Cancel} from './GameProposalAction';
 | 
					import {Accept} from './GameProposalAction';
 | 
				
			||||||
import './GameProposal.css';
 | 
					import Reject from './Reject'
 | 
				
			||||||
 | 
					import Cancel from './GameProposalCancel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const State = {
 | 
					import { AppData } from "../../context/data"
 | 
				
			||||||
	WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT",
 | 
					 | 
				
			||||||
	WaitForYou:      "GAME_PROPOSAL_WAIT_FOR_YOU",
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const GameProposal = ({games}) => {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (games == null)
 | 
					export default function GameProposal() {
 | 
				
			||||||
    return <p>Loading..</p>
 | 
					  const [data] = React.useContext(AppData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // for (const [key, value] of Object.entries(games))
 | 
					  if (data.games == null)
 | 
				
			||||||
 | 
					    return <div>Loading..</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // for (const [key, value] of Object.entries(data.games))
 | 
				
			||||||
  //   console.log(key, value);
 | 
					  //   console.log(key, value);
 | 
				
			||||||
   
 | 
					   
 | 
				
			||||||
  const waitForYou = games
 | 
					  const waitForYou = data.games
 | 
				
			||||||
    .filter(game => game.status === State.WaitForYou)
 | 
					    .filter(game => game.status === Status.WaitForYou)
 | 
				
			||||||
    .map(game => { 
 | 
					    .map(game => { 
 | 
				
			||||||
      return <div class="li" key={game.uuid}>
 | 
					      return <div className="li" key={game.uuid}>
 | 
				
			||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
          You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
 | 
					          You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
 | 
				
			||||||
          <br/>
 | 
					          <br/>
 | 
				
			||||||
@ -30,10 +31,10 @@ const GameProposal = ({games}) => {
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const WaitForOpponent = games
 | 
					  const WaitForOpponent = data.games
 | 
				
			||||||
    .filter(game => game.status === State.WaitForOpponent)
 | 
					    .filter(game => game.status === Status.WaitForOpponent)
 | 
				
			||||||
    .map(game => { 
 | 
					    .map(game => { 
 | 
				
			||||||
      return <div class="li" key={game.uuid}>
 | 
					      return <div className="li" key={game.uuid}>
 | 
				
			||||||
        <p> 
 | 
					        <p> 
 | 
				
			||||||
          You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
 | 
					          You {Stone(game.myColor)} <i>vs</i> {game.opponentName} {Stone(oppositeColor(game.myColor))}
 | 
				
			||||||
          <br/>
 | 
					          <br/>
 | 
				
			||||||
@ -47,7 +48,7 @@ const GameProposal = ({games}) => {
 | 
				
			|||||||
  return <div className="GameProposal">
 | 
					  return <div className="GameProposal">
 | 
				
			||||||
    {waitForYou}
 | 
					    {waitForYou}
 | 
				
			||||||
    {WaitForOpponent.length > 0 && 
 | 
					    {WaitForOpponent.length > 0 && 
 | 
				
			||||||
      <div class="separator">
 | 
					      <div className="separator">
 | 
				
			||||||
        waiting for opponent ({WaitForOpponent.length})
 | 
					        waiting for opponent ({WaitForOpponent.length})
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -55,14 +56,21 @@ const GameProposal = ({games}) => {
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Status = {
 | 
				
			||||||
 | 
						WaitForOpponent: "GAME_PROPOSAL_WAIT_FOR_OPPONENT",
 | 
				
			||||||
 | 
						WaitForYou:      "GAME_PROPOSAL_WAIT_FOR_YOU",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function Stone(color) {
 | 
					function Stone(color) {
 | 
				
			||||||
  if (color === "WHITE")
 | 
					  if (color === "WHITE")
 | 
				
			||||||
    return <span class="stone">⛀</span>
 | 
					    return <span className="stone">⛀</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (color === "BLACK")
 | 
					  if (color === "BLACK")
 | 
				
			||||||
    return <span class="stone">⛂</span>
 | 
					    return <span className="stone">⛂</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return <span class="stone">{color}</span>
 | 
					  return <span className="stone">{color}</span>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function oppositeColor(color) {
 | 
					function oppositeColor(color) {
 | 
				
			||||||
@ -74,5 +82,3 @@ function oppositeColor(color) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return color
 | 
					  return color
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default GameProposal;
 | 
					 | 
				
			||||||
							
								
								
									
										17
									
								
								webapp/src/components/Header/index.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								webapp/src/components/Header/index.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import './index.css';
 | 
				
			||||||
 | 
					import React from "react"
 | 
				
			||||||
 | 
					import { Link } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Header() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return <div>
 | 
				
			||||||
 | 
					    <h1>CordaCheckers</h1>
 | 
				
			||||||
 | 
					    <nav>
 | 
				
			||||||
 | 
					      <Link to="/leaderboard">Leaderboard</Link> {"| "}
 | 
				
			||||||
 | 
					      <Link to="/gameproposal">Game Proposal</Link> {"| "}
 | 
				
			||||||
 | 
					      <Link to="/game">Active Games</Link> {"| "}
 | 
				
			||||||
 | 
					      <Link to="/archive">Archive</Link> {"| "}
 | 
				
			||||||
 | 
					      <Link to="about">About</Link>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,22 +1,11 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import React from "react"
 | 
				
			||||||
import './Leaderboard.css';
 | 
					import './index.css';
 | 
				
			||||||
 | 
					import { AppData } from "../../context/data"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Leaderboard = () => {
 | 
					export default function Leaderboard() {
 | 
				
			||||||
 | 
					  const [data] = React.useContext(AppData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [data, setData] = useState(null);
 | 
					  if (data.leaderboard == null) 
 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    fetch('/api/leaderboard')
 | 
					 | 
				
			||||||
      .then((response) => response.json())
 | 
					 | 
				
			||||||
      .then((data) => {
 | 
					 | 
				
			||||||
        setData(data);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch((err) => {
 | 
					 | 
				
			||||||
        console.log(err.message);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (data == null) 
 | 
					 | 
				
			||||||
    return <p>Loading...</p>
 | 
					    return <p>Loading...</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//  var listItems = Object.keys(data).map(playerName => {
 | 
					//  var listItems = Object.keys(data).map(playerName => {
 | 
				
			||||||
@ -28,8 +17,8 @@ const Leaderboard = () => {
 | 
				
			|||||||
//  });
 | 
					//  });
 | 
				
			||||||
//  return <ul>{listItems}</ul>;
 | 
					//  return <ul>{listItems}</ul>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const tableRows = Object.keys(data).map(playerName => {
 | 
					  const tableRows = Object.keys(data.leaderboard).map(playerName => {
 | 
				
			||||||
    var rank = data[playerName];
 | 
					    var rank = data.leaderboard[playerName];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <tr key={playerName}>
 | 
					    return <tr key={playerName}>
 | 
				
			||||||
      <td>{playerName}</td>
 | 
					      <td>{playerName}</td>
 | 
				
			||||||
@ -55,5 +44,3 @@ const Leaderboard = () => {
 | 
				
			|||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Leaderboard;
 | 
					 | 
				
			||||||
							
								
								
									
										17
									
								
								webapp/src/context/app/index.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								webapp/src/context/app/index.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import React from "react"
 | 
				
			||||||
 | 
					import { reducer, initialState } from "./reducer"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AppContext = React.createContext({
 | 
				
			||||||
 | 
					  state: initialState,
 | 
				
			||||||
 | 
					  dispatch: () => null
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AppContextProvider = ({ children }) => {
 | 
				
			||||||
 | 
					  const [state, dispatch] = React.useReducer(reducer, initialState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <AppContext.Provider value={[ state, dispatch ]}>
 | 
				
			||||||
 | 
					    	{ children }
 | 
				
			||||||
 | 
					    </AppContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								webapp/src/context/app/reducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								webapp/src/context/app/reducer.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					export const reducer = (state, action) => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case "togglePolling":
 | 
				
			||||||
 | 
					      return { ...state,
 | 
				
			||||||
 | 
					        disablePolling: !state.disablePolling // on/off
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return state
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const initialState = {
 | 
				
			||||||
 | 
					  disablePolling: false,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								webapp/src/context/data/Poll.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								webapp/src/context/data/Poll.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					import { useState, useCallback, useEffect,  } from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Poll(url, interval_sec, disabled) {
 | 
				
			||||||
 | 
					    const [cache, setCache] = useState(null)
 | 
				
			||||||
 | 
					    const [fetching, setFetching] = useState(false)
 | 
				
			||||||
 | 
					    const [timeoutID, setTimeoutID] = useState(null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const fecthData = useCallback(() => {
 | 
				
			||||||
 | 
					        setTimeoutID(null)
 | 
				
			||||||
 | 
					        setFetching(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fetch(url)
 | 
				
			||||||
 | 
					            .then((response) => {
 | 
				
			||||||
 | 
					                setFetching(false)
 | 
				
			||||||
 | 
					                return response.json()
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .then((freshData) => setCache(freshData))
 | 
				
			||||||
 | 
					            .catch((err) => console.log(err.message))
 | 
				
			||||||
 | 
					    }, [url])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (cache == null) {
 | 
				
			||||||
 | 
					            fecthData()  // <<-- run immediatly on startup
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (disabled === true) {
 | 
				
			||||||
 | 
					            clearTimeout(timeoutID) // cancel already scheduled fetch
 | 
				
			||||||
 | 
					            setTimeoutID(null)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (timeoutID === null) {
 | 
				
			||||||
 | 
					            const timeoutID = setTimeout(fecthData, interval_sec * 1000)
 | 
				
			||||||
 | 
					            setTimeoutID(timeoutID)
 | 
				
			||||||
 | 
					            console.log("Fetch '" +url +"' scheduled in " +interval_sec +" sec")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [url, cache, fecthData, timeoutID, disabled, interval_sec]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        data: cache,
 | 
				
			||||||
 | 
					        fetching
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								webapp/src/context/data/index.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								webapp/src/context/data/index.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					import React from "react"
 | 
				
			||||||
 | 
					import { reducer, initialState } from "./reducer"
 | 
				
			||||||
 | 
					import { AppContext } from "../app"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Poll from "./Poll"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AppData = React.createContext({
 | 
				
			||||||
 | 
					  state: initialState,
 | 
				
			||||||
 | 
					  dispatch: () => null
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AppDataProvider = ({ children }) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [data, dispatchData] = React.useReducer(reducer, initialState)
 | 
				
			||||||
 | 
					  const [appContext] = React.useContext(AppContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const games = Poll('api/gamestate', 30, appContext.disablePolling)
 | 
				
			||||||
 | 
					  const leaderboard = Poll('api/leaderboard', 60, appContext.disablePolling)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  data.games       = games.data
 | 
				
			||||||
 | 
					  data.leaderboard = leaderboard.data
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  var fetching = []
 | 
				
			||||||
 | 
					  if (games.fetching === true)
 | 
				
			||||||
 | 
					    fetching =  [...fetching, "games"]
 | 
				
			||||||
 | 
					  if (leaderboard.fetching === true)
 | 
				
			||||||
 | 
					    fetching =  [...fetching, "leaderboard"]
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  data.fetching = fetching
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <AppData.Provider value={[data, dispatchData]}>
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </AppData.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								webapp/src/context/data/reducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								webapp/src/context/data/reducer.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					export const reducer = (state, action) => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      console.warn("Unknown action.type", action)
 | 
				
			||||||
 | 
					      return state
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					export const initialState = { 
 | 
				
			||||||
 | 
					  games: null,
 | 
				
			||||||
 | 
					  leaderboard: null,
 | 
				
			||||||
 | 
					  fetching: []
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,14 +1,21 @@
 | 
				
			|||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import ReactDOM from 'react-dom/client';
 | 
					import ReactDOM from 'react-dom/client';
 | 
				
			||||||
import './index.css';
 | 
					import './index.css';
 | 
				
			||||||
import App from './App';
 | 
					 | 
				
			||||||
import reportWebVitals from './reportWebVitals';
 | 
					import reportWebVitals from './reportWebVitals';
 | 
				
			||||||
 | 
					import App from './App';
 | 
				
			||||||
 | 
					import { AppDataProvider } from "./context/data"
 | 
				
			||||||
 | 
					import { AppContextProvider } from "./context/app"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
 | 
					const root = ReactDOM.createRoot(document.getElementById('root'));
 | 
				
			||||||
root.render(
 | 
					root.render(
 | 
				
			||||||
  <React.StrictMode>
 | 
					
 | 
				
			||||||
    <App />
 | 
					  //<React.StrictMode>
 | 
				
			||||||
  </React.StrictMode>
 | 
					  <AppContextProvider>
 | 
				
			||||||
 | 
					    <AppDataProvider>
 | 
				
			||||||
 | 
					      <App />
 | 
				
			||||||
 | 
					    </AppDataProvider>
 | 
				
			||||||
 | 
					  </AppContextProvider>
 | 
				
			||||||
 | 
					  //</React.StrictMode>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// If you want to start measuring performance in your app, pass a function
 | 
					// If you want to start measuring performance in your app, pass a function
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user