I am making a chess game and I need to integrate a count down timer, e.g - so when it's the white players turn their clock is counting down until they move.
To avoid changing a lot more code that is not present in this question ,I would like to keep function "move" as is, though I could change CountDownTimer if needed,
For now I would like to know how to call the react hook setTimerOn in the CountDownTimer component from the function move,then I can work out the rest myself.
From what I have read you cannot do this with react hooks? so just looking for
anything that works, if that means changing CountDownTimer.
export function move(from, to, promotion) {
let tempMove = { from, to }
if (member.piece === chess.turn()) {
const legalMove = chess.move(tempMove)
if (legalMove) {
updateGame()
//i would like to access CountDownTimer and call setTimerOn(true)
// i can work out the code for white and black / start and stop myself afterwards
}
}
,
}
import React from 'react'
function CountDownTimer(){
const [time, setTime] = React.useState(0);
const [timerOn, setTimerOn] = React.useState(false);
React.useEffect(() => {
let interval = null;
if (timerOn) {
interval = setInterval(() => {
setTime((prevTime) => prevTime + 10);
}, 10);
} else if (!timerOn) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [timerOn]);
return (
<div className="Timers">
<h2>Stopwatch</h2>
<div id="display">
<span>{("0" + Math.floor((time / 60000) % 60)).slice(-2)}:</span>
<span>{("0" + Math.floor((time / 1000) % 60)).slice(-2)}:</span>
<span>{("0" + ((time / 10) % 100)).slice(-2)}</span>
</div>
<div id="buttons">
{!timerOn && time === 0 && (
<button onClick={() => setTimerOn(true)}>Start</button>
)}
{timerOn && <button onClick={() => setTimerOn(false)}>Stop</button>}
{!timerOn && time > 0 && (
<button onClick={() => setTime(0)}>Reset</button>
)}
{!timerOn && time > 0 && (
<button onClick={() => setTimerOn(true)}>Resume</button>
)}
</div>
</div>
);
});
export default CountDownTimer; ```
Based on React documentation, React Hook can be called from function components or other hooks.
In your situation, you should consider the utilization of React Context. You need to move up timerOn state and setTimerOn method as context values. So that, all components which are wrapped by the context provider can utilize the values.
First, create some helpers for managing context.
// TimerOn.js
import React, { createContext, useContext } from 'react';
// create a context
const TimerOnContext = createContext();
// create a hook to provide context value
export function useTimerOn() {
const contextValue = useContext(TimerOnContext);
return contextValue;
}
// custom provider that will wrap your main components
export function TimerOnProvider({ children }) {
const [timerOn, setTimerOn] = React.useState(false);
return (
<TimerOnContext.Provider value={{ timerOn, setTimerOn }}>
{children}
</TimerOnContext.Provider>
);
}
For instance, I create two simple components to demonstrate the timer component and caller component.
// CountDownTimer.js
import React from "react";
import { useTimerOn } from "./TimerOn";
export default function CountDownTimer() {
const { timerOn } = useTimerOn();
// detect changes
React.useEffect(() => {
if (timerOn) {
console.log('timer is on');
} else {
console.log('timer is off');
}
}, [timerOn]);
return (
<div>{timerOn ? 'timer on' : 'timer off'}</div>
);
}
// MoveCaller.js
import React from "react";
import { useTimerOn } from "./TimerOn";
export default function MoveCaller() {
const { timerOn, setTimerOn } = useTimerOn();
// move then set timer
const move = () => {
setTimerOn(!timerOn);
}
return (
<div>
<button type="button" onClick={move}>
Move
</button>
</div>
);
}
Then, you can wrap all main components with the provider component. So, the move function in a component can change the state of timerOn and be read by another component.
import React from 'react';
import CountDownTimer from './CountDownTimer';
import MoveCaller from './MoveCaller';
import { TimerOnProvider } from './TimerOn';
export default function ChessApp() {
return (
<TimerOnProvider>
<CountDownTimer />
<MoveCaller />
</TimerOnProvider>
);
}
Related
I know that we can create multiple custom hooks in separate files for example
useCounter.js and useToggle.js.
My Question
Can we create a Javascript class with its methods as custom hooks and use it in our functional components. Is this an anti-pattern ? The reason I am thinking to put it in a JS class is so that I don't have to create multiple files for custom hook.
Something Like below
class CustomHooks {
useCounter() {
const [count, setCount] = React.useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
useToggle(initialState = false) {
const [state, setState] = React.useState(initialState);
const toggle = () => setState(!state);
return { state, toggle };
}
}
And use it like below
const hooks = new CustomHooks();
const Counter = () => {
const { count, increment, decrement } = hooks.useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
const Toggler = () => {
const { state, toggle } = hooks.useToggle();
return (
<div>
<p>Toggler is {state ? "on" : "off"}</p>
<button onClick={toggle}>Toggle</button>
</div>
);
};
const App = () => {
return (
<div>
<Counter />
<Toggler />
</div>
);
};
You cannot use React hooks in class. If your goal is not to have multiple files for multiple hooks, create a single file and export multiple hooks from it.
// hooks.js
export const useToggle = () => {
// hook implementation
}
export const useCounter = () => {
// hook implementation
}
And use them in your components
// App.js
import { useToggle, useCounter } from './hooks';
function App() {
const toggle = useToggle();
const counter = useCounter();
return <div>hi there</div>
}
You can simply create an object that will hold the hooks.
customhooks.js
function useCounter () {
...
}
function useToggle(initialState = false) {
...
}
export const hooks = {
useCounter,
useToggle
}
counter.js
import { hooks } from "./customhooks.js";
const Counter = () => {
const { count, increment, decrement } = hooks.useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};
However, personally I recommend handling with individual files per hook. Especially when the project gets bigger and we have many hooks, we eventually get to the point that we need different hooks in different areas and it can be detrimental to performance if all hooks have to be loaded at once. Of course, this is not only related to the number but also to the complexity of the hooks and what dependencies they have in turn.
I need to call function resetToken() from another page when i click on button.
resetToken() should change useState to generate new code. I don't know how to import this function to another page and use it.
I have import
import Captcha from '../../../components/Captcha/Captcha'; and displayed with <Captcha/> in return( ... )
So when i click on button I need to call function resetToken() to generate new code or call again import because I have in <Captcha/>
React.useEffect(() => {
resetToken();
},[]);
This code is Captcha.jsx
import React from 'react';
import './Captcha.css';
function Captcha({statusOfCaptcha}){
const [status, setStatus] = React.useState(undefined);
const [code, setCode] = React.useState(undefined);
const [text, setText] = React.useState("");
const [seconds, setSeconds] = React.useState(120);
function resetToken(){
//generate code
var codeGenerated = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz123456789";
for (var i = 0; i < 6; i++){
codeGenerated += possible.charAt(Math.floor(Math.random() * possible.length));
}
setCode(codeGenerated);
//reset every 120 second
setInterval(function(){
var codeGenerated = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz123456789";
for (var i = 0; i < 6; i++){
codeGenerated += possible.charAt(Math.floor(Math.random() * possible.length));
}
setCode(codeGenerated);
setSeconds(120);
setStatus(undefined);
setText("");
}, 120000);
const interval = setInterval(() => {
setSeconds(seconds => seconds - 1);
}, 1000);
return () => clearInterval(interval);
}
React.useEffect(() => {
resetToken();
},[]);
function checkCaptcha(e){
if(e === code){
setStatus(true);
statusOfCaptcha(true);
} else{
setStatus(false);
statusOfCaptcha(false);
}
}
return (
<div className='captcha'>
<div className="background">
<p onCopy={(e) => e.preventDefault()} className="unselectable">{code}</p>
<a>{seconds}</a>
</div>
<div className='input-captcha'>
<input type="text" placeholder="Zadejte kód" value={text} onChange={(e) => {checkCaptcha(e.target.value); setText(e.target.value)}}/>
{status === false && (<i class='bx bx-x text-color-red'></i>)}
{status === true && (<i class='bx bx-check text-color-green'></i>)}
</div>
</div>
)
}
export default Captcha;
This code is index.jsx
import React from 'react'
import Captcha from '../../../components/Captcha/Captcha';
function Index() {
function change(){
//here i need to call function from Captcha.jsx - resetToken();
}
return (
<div>
<Captcha statusOfCaptcha={resCaptchaData}/>
<button onclick={change}>Reset captcha code</button>
</div>
)
}
export default Index
It would be better to use a custom hook, to store your state, and resetToken function, So you can use it in multiple places.
For more resources about custom hooks.
https://reactjs.org/docs/hooks-custom.html
You can do this in several ways
for example you can use state manager like context api or redux.
In order to have access to your states or functions everywhere and in all pages and components
Or you can put the resetToken function in the parent component and have access to it in the child components.
export const ParentComponent = (children) => {
function resetToken {
....
}
return (
<Recapcha resetToken={resetToken} />
)
}
const Recapcha = ({resetToken}) => {
return (...)
}
import { useState, useEffect } from 'react'
import './App.css';
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('render')
}, [count])
First: show me on UI but send me error on conosle: Too many re-renders. React limits the number of renders to prevent an infinite loop.
const plusCount = () => {
setCount(count + 1) }
const minsCount = () => {
setCount(count - 1) }
Second : do not sho em on UI send me error on UI: Too many re-renders. React limits the number of renders to prevent an infinite loop.
const makeCount = {
add:setCount(count + 1),
discount: setCount(count - 1)
}
return (
<h1>Exercise</h1>
<p>Cunt: <b>{count}</b></p>
<button onClick={plusCount}>Add</button>
<button onClick={minsCount}>Discount</button>
</div>
)
}
export default App;
Guestion:
Why is this message show me error on both time, but on first let me show on UI
on the second do not show me on UI
You are executing the setCount function on render, which causes a rerender which results in an infinity loop:
const makeCount = {
add: setCount(count + 1),
discount:setCount(count - 1)
}
This object actually call the setCount function instead of creating an fucntion to be called.
You need to change it to:
const makeCount = {
add: () => setCount(count + 1),
discount: () => setCount(count - 1)
}
This will generate new functions called add and discount instead of calling setCount.
App.js
import React ,{useState} from 'react';
import { Child } from './Components/Child';
function App() {
let value = [1,2,4,6];
const number = (number,val)=>{
console.log(`${number}: value ${val}`)
}
return (
<div className="App">
{
value.map((item , i)=>{
return <Child count = {item} itemName={i} key={i} muFunc={number}/>
})
}
</div>
);
}
export default App;
Child.js
import React,{useState,useEffect} from 'react';
export function Child ({count,itemName,muFunc}) {
const [number, setnumber] = useState(count);
useEffect(() => {
muFunc(itemName,number);
}, [number]);
const makeCount = {
add: () => setnumber(number + 1),
discount: () => setnumber(number - 1)
}
// Send this number to parent ??
return(
<>
<h3>{itemName}</h3>
<button onClick ={makeCount.discount}>decrement</button>
<input value={number} onChange={(e)=>setnumber(e.target.value)} />
<button onClick ={makeCount.add}>Increment</button>
<br/>
</>
)
}
I'm trying to play around for the first time with the WebAudio API together with React.
My idea was to build a simple button that, once clicked, would start or stop a sound.
With the following code, I'm always getting the error "Failed to execute 'disconnect' on 'AudioNode': the given destination is not connected."
How can I fix it?
Thanks!
import { useState } from 'react';
function App() {
const [ dataPlaying, setDataPlaying ] = useState(false)
const AudioContext = window.AudioContext || window.webkitAudioContext
const audioContext = new AudioContext()
let osc = audioContext.createOscillator()
osc.type = 'sine'
osc.frequency.value = 880
const createOscillator = () => {
if (dataPlaying === false) {
osc.start()
osc.connect(audioContext.destination)
setDataPlaying(true)
} else {
osc.disconnect(audioContext.destination)
setDataPlaying(false)
}
}
return (
<div className="App">
<button
onClick={() => createOscillator() }
data-playing={ dataPlaying }>
<span>Play/Pause</span>
</button>
</div>
);
}
export default App;
Here's my attempt to resolve the connection error.
Create the AudioContext external to the component.
Use an useRef hook to store an audio context to persist through rerenders.
Use an useEffect hook to instantiate an oscillator and manage audo context connection.
Use start/stop toggler to suspend or resume the context instead of connect/disconnect from it.
Updated Code
import React, { useEffect, useRef, useState } from "react";
const AudioContext = window.AudioContext || window.webkitAudioContext;
export default function App() {
const [dataPlaying, setDataPlaying] = useState(false);
const audioContextRef = useRef();
useEffect(() => {
const audioContext = new AudioContext();
const osc = audioContext.createOscillator();
osc.type = "sine";
osc.frequency.value = 880;
// Connect and start
osc.connect(audioContext.destination);
osc.start();
// Store context and start suspended
audioContextRef.current = audioContext;
audioContext.suspend();
// Effect cleanup function to disconnect
return () => osc.disconnect(audioContext.destination);
}, []);
const toggleOscillator = () => {
if (dataPlaying) {
audioContextRef.current.suspend();
} else {
audioContextRef.current.resume();
}
setDataPlaying((play) => !play);
};
return (
<div className="App">
<button onClick={toggleOscillator} data-playing={dataPlaying}>
<span>{dataPlaying ? "Pause" : "Play"}</span>
</button>
</div>
);
}
import React from 'react';
import {Plugins} from '#capacitor/core';
import {useState, useEffect} from 'react';
import {db} from './Firebase';
const Maps = () => {
const [lat, setLat] = useState(0);
const [long, setLong] = useState(0);
const [count, setCount] = useState (0);
const Counter = () => {
setCount(count + 1)
console.log(count)
}
const Location = () => {
Plugins.Geolocation.getCurrentPosition().then(
result => setLat ( result.coords.latitude)
)
Plugins.Geolocation.getCurrentPosition().then(
result => setLong (result.coords.longitude)
)
}
const interval = () => {
setInterval (() =>
{
Location();
Counter();
}, 5000 );
}
return (
<div>
<div>
<button onClick = {interval}>
Get Location
</button>
</div>
<div>
{long}
</div>
<div>
{lat}
</div>
</div>
)
}
export default Maps;
I'm trying to get the counter to increment on every iteration of setInterval, through the counter function, but when I log count, it does not increment and always remains as 0.
I've tried running setCount itself within setInterval without any success, it still does not increment count.
Its a stale closure. Change to this setCount(prevCount => prevCount + 1).
Using the updater form of set state like above, you can guarantee that you will be using the most recent value of state.
You can think of it as count in your function being a snapshot of what its value was when the setInterval was declared. This will stop your updates from appearing to work.
In addition, setting state is async, so the console.log(count) will most likely not reflect the new value. Log in an effect or outside the function body to see the updated value each render.
A note about your implementation:
You are creating a setInterval each time the button is clicked. This could lead to some interesting side-effects if clicked more than once. If you click the button twice for example, you will have two setIntervals running every 5 seconds.
In addition to #BrianThompson answer. Try this to avoid innecessary rerenders
import React from 'react';
import {Plugins} from '#capacitor/core';
import {useState, useEffect} from 'react';
import {db} from './Firebase';
const Maps = () => {
const [state, setState] = useState({
latLng:{lat:0,lng:0},
counter: 0
})
const interval = useRef()
//Use camelCase for methods
const location = () => {
Plugins.Geolocation.getCurrentPosition().then(
result => setState ( ({counter}) => {
counter = counter+1
console.log(counter)
return ({
latLng: {
lat: result.coords.latitude,
lng: result.coords.longitude
},
counter
})
})
)
}
const startInterval = () => {
if(interval.current) return;
interval.current = setInterval (() => {
location();
}, 5000 );
}
const stopInterval = () ={
clearInterval(interval.current)
interval.current = null
}
useEffect(()=>{
//Because interval is causing state updates, remember to clear interval when component will unmount
return stopInterval
},[])
return (
<div>
<div>
<button onClick = {startInterval}>
Get Location
</button>
</div>
<div>
{state.latLng.lng}
</div>
<div>
{state.latLng.lat}
</div>
</div>
)
}