Next.js text cycling component never updates - reactjs

I'm trying to write what I thought would be a simple component: it takes an array of strings, and every three seconds the text in the div of the component is changed to the next item in the array, looping back to the beginning.
Although the console shows that the change message function is run every three seconds, the message never changes. I presume this is because the useState update never happens. Why is this, and how do I get it to work?
// components/textCycle.js
import { useState, useEffect} from 'react';
function TextCycle ( { array } ) {
const [ msg, setMsg ] = useState(0);
useEffect(() => {
function changeMsg() {
setMsg((msg > array.length) ? 0 : msg + 1);
}
setInterval( changeMsg, 3000);
}, []);
return (
<div>
{ array[msg] }
</div>
);
};
export default TextCycle;

// components/textCycle.js
import { useState, useEffect} from 'react';
function TextCycle ( { array } ) {
const [msg, setMsg] = useState(0);
function changeMsg() {
setMsg((msg > array.length - 2) ? 0 : msg + 1);
}
useEffect(() => {
setTimeout(changeMsg, 1000);
}, [msg]);
return (
<div>
{array[msg]}
</div>
);
};
export default TextCycle;

Related

React custom hook not rendering

I am building an app with React using Remix, fly.io for deployment. I have a custom React hook useCountdown that has the following code:
import { useEffect, useState } from 'react';
const useCountdown = (targetSeconds) => {
const countDownSeconds = targetSeconds
const [countDown, setCountDown] = useState(countDownSeconds);
useEffect(() => {
const interval = setInterval(() => {
setCountDown(countDownSeconds);
return () => clearInterval(interval);
}, [countDownSeconds]);
return getReturnValues(countDown);
}, [])
};
const getReturnValues = (countDown) => {
const seconds = Math.floor((countDown % (1000 * 60)) / 1000);
return [seconds];
}
export { useCountdown }
The DateTimeDisplay component has the following code and is a component dependency of the hook:
import React from 'react';
const DateTimeDisplay = ({ value, type, isDanger }) => {
return (
<div className={isDanger ? 'countdown danger' : 'countdown'}>
<p>{value}</p>
<span>{type}</span>
</div>
);
};
export default DateTimeDisplay;
Finally, I have a CountdownTimer component that has the following code:
import React from 'react';
import DateTimeDisplay from './DateTimeDisplay';
import ExpiredNotice from './ExpiredNotice'
import { useCountdown } from '../hooks/useCountdown';
const ShowCounter = ({ seconds }) => {
return (
<div className="show-counter">
<DateTimeDisplay value={seconds} type={'seconds'} isDanger={false} />
</div>
);
};
const CountdownTimer = ({ targetSeconds }) => {
const [seconds] = useCountdown(targetSeconds);
if (seconds <= 0) {
return <ExpiredNotice />;
} else {
return (
<ShowCounter
seconds={seconds}
/>
);
}
};
export default CountdownTimer;
Upon trying to utilize the useCountdown() hook, I get the following error:
TypeError: useCountdown is not a function or its return value is not iterable
at CountdownTimer (/Users/tduke/Desktop/dev/drawesome/app/components/CountdownTimer.jsx:17:23)
at processChild (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3353:14)
at resolve (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3270:5)
at ReactDOMServerRenderer.render (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3753:22)
at ReactDOMServerRenderer.read (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3690:29)
at renderToString (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:4298:27)
at handleRequest (/Users/tduke/Desktop/dev/drawesome/app/entry.server.jsx:10:16)
at handleDocumentRequest (/Users/tduke/Desktop/dev/drawesome/node_modules/#remix-run/server-runtime/server.js:400:18)
at requestHandler (/Users/tduke/Desktop/dev/drawesome/node_modules/#remix-run/server-runtime/server.js:49:18)
at /Users/tduke/Desktop/dev/drawesome/node_modules/#remix-run/express/server.js:39:22
The line in question:
const [seconds] = useCountdown(targetSeconds);
Can someone explain to me this error, and what it exactly is telling me so I understand this error in it's entirety, and what the cause is in this instance?
How do I fix it?
useCountdown does not have a return statement, so it's implicitly returning undefined. Then when you try to destructure undefined, you get that error because array destructuring only works on arrays (or other iterables, which is why the error mentions "iterable"s, not arrays)
You did put a return statement inside your useEffect, but returning something in a useEffect is for cleaning up the effect. It won't cause useCountdown to return anything. To fix this, move your return statement to the body of useCountdown, and move the effect cleanup to be the return from the effect:
const useCountdown = (targetSeconds) => {
const countDownSeconds = targetSeconds
const [countDown, setCountDown] = useState(countDownSeconds);
useEffect(() => {
const interval = setInterval(() => {
setCountDown(countDownSeconds);
}, [countDownSeconds]);
return () => clearInterval(interval);
}, [])
return getReturnValues(countDown);
};

Using function defined in custom hook does not update some value in the state

i recently started using react and i'm trying to write a custom hook used for translating some ui elements. I'm not using a library because it is a larger project and it is expected to have a lot of custom loading/ translating parts.
Goal:
call the hook in every translatable component (in a very light syntax)
the hook will return a function used to translate elements in that component
So far, i tried this https://codesandbox.io/s/react-playground-forked-jt90ii?file=/MyComponent.js
Here is the custom hook:
import { useState, useEffect } from "react";
export default function useTranslation({ className, callbackFunction }) {
const [translations, setTranslations] = useState([]);
useEffect(() => {
async function fetchTranslationsForClass(
endpoint,
setTranslations,
className
) {
// HERE WILL BE API CALL
setTranslations([{ code: "HM", value: "Home" }]);
if (callbackFunction) callbackFunction();
}
fetchTranslationsForClass("", setTranslations, className);
}, []);
return [
(code) => {
var a = internalT(translations, code);
return a;
},
translations,
setTranslations
];
}
function internalT(translations, translationCode, interpolationParam) {
var tr = translations.filter((x) => x.code == translationCode);
if (!interpolationParam) return tr && tr[0] && tr[0].value;
else return tr && tr[0] && strInterpolate(tr[0].value);
}
const strInterpolate = (template, args = {}) => {
const interpolateHandler = new Function(
"params",
"const __ = (" +
Object.keys(args).join(",") +
") => `" +
template +
"`\nreturn __(...Object.values(params))"
);
return interpolateHandler(args);
};
That i'm trying to use like this:
import useTranslation from "./useTranslation";
import MyComponent2 from "./MyComponent2.js";
import { useState } from "react";
export default function MyComponent() {
const [t, tr, setTr] = useTranslation("MyComponent", () => {});
const [translatedItems, setTranslatedItems] = useState([{ name: t("HM") }]);
return (
<>
<div>{t("HM")}</div>
<MyComponent2 translatedItems={translatedItems} />
</>
);
}
And it works fine for string embedded in ui but it does not work for a string passed in component state in translatedItems;
In MyComponent2 item.name is always undefined:
export default function MyComponent2({ translatedItems }) {
return (
<>
{translatedItems.map((item, index) => {
return (
<>
<div>{"index" + index}</div>
<div key={index}> {"'" + item.name + "'"}</div>
</>
);
})}
</>
);
}
Basically the state is initialized at first render and it does not update after the 't' function is defined;
If i try to set the state with useEffect this will create an infinite loop.

Calling react hook from a function on another page

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>
);
}

Simple countdown - onClick button fires on render

I'm trying to build a very simple 5 second countdown in React and can't seem to get it to work. In the code below, the timer starts as soon as the app renders for the first time, and then once it gets past 0 it seems to flicker and break down.
Any suggestions?
import './styles/App.css';
import { useState } from 'react';
function App() {
const [ time, setTime ] = useState(5);
const startCountdown = setInterval(() => {
if (time < 0 ) {
clearInterval(startCountdown);
}
setTime(time - 1);
}, 1000);
return (
<div className="App">
<h1>{time}</h1>
<button onClick={startCountdown}>Start</button>
</div>
);
}
export default App;
You need to pass it as a function. Currently your assigning this as code block which will be executed straightway.
exp:
const startCountdown = () => setInterval(() => {
if (time < 0 ) {
clearInterval(startCountdown);
}
setTime(time - 1);
}, 1000);

Unexpected useEffect behaviour

I am making hangman game. When letter is clicked it should not be clickable any more.
Here' a simplified version on codesandbox:
https://codesandbox.io/s/intelligent-cori-6b80q
In the sandbox, when letter is clicked it changes to "CLICKED!". Everything seems well here.
But in my project I get strange behavior.
AvailableLetter.js
const AvailableLetter = (props) => {
const [clicked,setClicked]=useState(false);
const setStuff = () => {
setClicked(false); // If I delete this I get Error: Maximum update depth exceeded.
props.setSolved();
};
useEffect( setStuff,[clicked] );
if (clicked) // IF CLICKED REMAINS TRUE, EVERY LETTER GETS PLAYED.
{
if (props.play())
{
props.correct();
}
else
{
props.incorrect();
}
}
const disableLetter = () => {
setClicked(true);
};
let letter=span onClick={disableLetter}>{props.alphabet}</span>;
if(clicked) // CODE NEVER GETS HERE!!!!!!!!!!!!!!
{
letter = <span>{props.alphabet}</span>;
}
return (
<Ax> // Ax is a high order class, just a wrapper
{letter}
</Ax>
);
}
Letter remain clickable even after being clicked. This is not what I want.
Each letter is rendered by Letters.js which feeds in a-z and generates custom AvailableLetter.
const availableLetters = [ ...allLetters ].map(
(alphabet,i) => {
return (
<AvailableLetter setSolved={props.setSolved} play={()=>playHandler(alphabet)} correct={()=>props.correct(alphabet)} incorrect={()=>props.incorrect(alphabet)} solution={props.solution} key={i} alphabet={alphabet} />
);
}
);
So issues to be solved here are:
- Letters remain clickable even after a click
- If setClicked(false) is removed it causes an infinite loop
const setStuff = () => {
setClicked(false); // if removed causes infinite loop
props.setSolved();
};
All of this is strange because in the codesandbox I don't need to set clicked to false inside setEffect().
You can see all of the code here: https://github.com/gunitinug/hangmanerrors/tree/master/src
Please have a look at project code, it is not long.
You need to understand how useEffect works. Whenever the state changes, useEffect gets called and the handler (in your case setStuff) passed as a callback to useEffect gets executed.
So, setStuff keeps updating the state and useEffect keeps getting called continuously. Hence, you see the error Error: Maximum update depth exceeded.
Change your code as shown below:
const AvailableLetter = (props) => {
const [clicked, setClicked] = useState(false);
const setStuff = () => {
if(clicked) {
if (props.play()) {
props.correct();
}
else {
props.incorrect();
}
setClicked(false);
props.setSolved();
}
};
useEffect( setStuff, [clicked] );
return (
<Ax>
<button onClick={() => setClicked(true)} disabled={clicked}>{props.alphabet}</button>
</Ax>
);
}
As mentioned in the comment posted on your question, this might not be the best approach but my code should solve your problem.
It was a structural problem, I had to delegate handler functions and state to the parent component Letters.js
I got to make it work silky smooth :p
import React, {useState} from 'react';
import AvailableLetter from './AvailableLetter/AvailableLetter';
import DisabledLetter from './DisabledLetter/DisabledLetter';
import classes from './Letters.module.css';
const Letters = (props) => {
const [lettersMap, setLettersMap]=useState(
{
"a":false,"b":false,"c":false,"d":false,"e":false,"f":false,"g":false,"h":false,"i":false,"j":false,"k":false,"l":false,"m":false,"n":false,"o":false,"p":false,"q":false,"r":false,"s":false,"t":false,"u":false,"v":false,"w":false,"x":false,"y":false,"z":false
}
);
const updateClickedHandler = (letter) => {
setLettersMap(
{
...lettersMap,[letter]:true
}
);
};
const playHandler = (alphabet) => {
const solution = props.solution.split('');
console.log(solution);
if (solution.indexOf(alphabet)<0)
{
console.log('incorrect');
return false;
}
else
{
console.log('correct');
return true;
}
}
const renderedLetters = Object.keys(lettersMap).map(
(letter,i)=>{
if (!lettersMap[letter]) //letter is not yet clicked
{
return (
<AvailableLetter updateClicked={updateClickedHandler} setSolved={props.setSolved} play={()=>playHandler(letter)} correct={()=>props.correct(letter)} incorrect={()=>props.incorrect(letter)} solution={props.solution} key={i} alphabet={letter} />
)
}
else //letter is clicked
{
return (
<DisabledLetter alphabet={letter} />
)
}
}
);
return (
<div className={classes.Letters}>
<p>Solution: {props.solution}</p>
<div className={classes.AvailableLetters}>
{renderedLetters}
</div>
</div>
);
}
export default Letters;
and
import React from 'react';
import classes from './AvailableLetter.module.css';
import Ax from '../../hoc/Ax';
const AvailableLetter = (props) => {
const setStuff = () => {
if (props.play()) {
props.correct();
}
else {
props.incorrect();
}
props.updateClicked(props.alphabet);
props.setSolved();
};
return (
<Ax>
<span className={classes.AvailableLetter} onClick={setStuff}>{props.alphabet}</span>
</Ax>
);
}
export default AvailableLetter;
and finally, I added another component called DisabledLetter.js
import React from 'react';
import classes from './DisabledLetter.module.css';
const DisabledLetter = (props) => {
return (
<span className={classes.DisabledLetter} >{props.alphabet}</span>
);
};
export default DisabledLetter;
You can view entire source here
https://github.com/gunitinug/hangmanclicked/tree/master/src
;)
Discovered an issue though, when game is solved I need to click one more letter for 'YOU WIN' message to display.
When I die 'GAME OVER' is displayed immediately.

Resources