How to declare multiple variable useRef with a loop? Is that possible - reactjs

I need to clean up the code to be DRY from my senior.
Initially my logic and code is already working but senior want me to improve my code.
My code is something like this
const firstTextInputRef = useRef(null);
const secondTextInputRef = useRef(null);
const thirdTextInputRef = useRef(null);
const fourthTextInputRef = useRef(null);
const fifthTextInputRef = useRef(null);
const sixthTextInputRef = useRef(null);
const onOtpKeyPress = index => {
return ({nativeEvent: {key: value}}) => {
// auto focus to previous InputText if value is blank and existing value is also blank
if (value === 'Backspace' && otpArray[index] === '') {
if (index === 1) {
firstTextInputRef.current.focus();
} else if (index === 2) {
secondTextInputRef.current.focus();
} else if (index === 3) {
thirdTextInputRef.current.focus();
} else if (index === 4) {
fourthTextInputRef.current.focus();
} else if (index === 5) {
fifthTextInputRef.current.focus();
}
}
};
};
What I am doing now is I am listening to keypress and change the focus of the keyboard from 1 input to another.
What he want me to do is I need to declare the ref inside a loop and improve the if else statement
This snippet is what he suggest.
let otpTextInputs = [];
for(let i = 0; ;i!=5;i++) {
// put new ref into the array
}
I already tried multiple way and spend more that 2 hours on it and I still don't know how to do it. Can anyone suggest an answer?

You can just create a useEffect that runs when you first initialize your component that generates the inputs programmatically and sets their ref. That way you can set up a state where you can store all the refs and then access them.
Here is a CodeSandbox demo with the proposed solution.

Related

React state is not updating immediately after setState is being called

I am building the frontend of a web based software and I want to add new note every time I press add button.
But it's simply not happening. New note is being rendered only when I change the state of another object. Right below I ma attaching the code. Please help, I am stuck here.
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = allnotes;
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
setAllNotes(notesAllTemp)
}
});
}
If anyone can figure this out, please help.
Please don't make mistake by directly updating the array element by its index you should first copy the array into a new array, otherwise, it will directly update the array which will cause reacjs to not to re-render the component.
Check this out
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = [...allnotes]; // !IMPORTANT to copy array
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
}
});
setAllNotes(notesAllTemp);
}
Better if you first modify the array then update at once
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = allnotes;
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
}
});
setAllNotes(notesAllTemp)
}

Should I use useEffect in this situation?

I don't know if I am allowed to ask questions like these, but I have a dilemma where I don't know should I use useEffect in the situation I have here:
const handleUrlQuery = () => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
return {
queryName,
queryValue,
};
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
return {
queryName,
queryValue,
};
}
};
handleUrlQuery();
const url = `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
const { data, error } = useFetch(url);
This function is used for changing the query part of the url, now it is supposed to change the queryName and queryValue based on the search value or in this case debounced search value. Now I am confused because I have a feeling that I need to use useEffect, but I am not sure, anyone has any advice on this?
If you really want to optimize this code, which unless its in a super heavy component, I don't see too much of a need you could use useMemo.
const url = useMemo(() => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
}
return `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
}, [params.debouncedValue, params.page]);
// don't believe you have to add process.env.REACT_APP_API_URL as a dependency
const { data, error } = useFetch(url);
When you don't call the function handleUrlQuery inside a useEffect, it will be called on every re-render, even if params.debouncedValue didn't change.
Therefore, you need a useEffect if you have other state variables changing, and you only want to call handleUrlQuery when specifically params.debouncedValue changes.
Dummy Codesandbox example

Can I use ReactElement as useState argument?

I'm new in React and I wonder is using ReactElement as useState argument normal?
I try to do it and everything works fine. Is it anti-pattern or it's OK?
Unfortunately, I didn't find any information about it in documentation
const [infoBox, setInfobox] = useState<ReactElement|null>(null);
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
...
useEffect(() => {
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
} else {
setInfobox(null);
return;
}
setInfobox(<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>);
}, [catalogLoadedDataEmpty, catalogHasErrors]);
You can, but it's easy to create bugs where you expect the page to update, but it doesn't, because you forgot to update the state. It's usually better to save data in state, and then use that data to render fresh elements on each render.
And in your case i'd go one step further: this shouldn't be a state variable at all. The values catalogLoadedDataEmpty and catalogHasErrors are enough to determine the desired output directly. You can thus remove the use effect, and in so doing get rid of the double-render that you currently have:
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
}
const infoBox = infoBoxTitle ? (
<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>
) : null

React how to wait until state is updated without extra state variables?

I cannot understand. To cut it short, I have these 2 variables which are being used in the state
questionLoaded
gameOver
This component needs to wait until it finishes getting data using the function selectRandomQuestion.
Therefore if i remove the questionLoaded, it will give me exception when it's looping through the map.
Now I had to create another variable to check when the game is over to unrender this component, therefore the terniary condition questionLoaded || gameOver ?
I tried checking the variable currentQuestion to conditionally render, but for some reason it will give exception when looping through the map in the options array of this property. Which means currentQuestion gets data first before it's own child property options has any data when hitting the condition check
The problem is that this looks kind of dirty, I'm still using react just for a few weeks and I'm not sure if this is the appropriate way to deal with state updates or is there a better way to handle it.
To sum it up, my problem is when rendering components, I often need to wait for the state to update when the rendering depends on some conditions, and everytime I need a new condition that means I need another set of logic using another state variable. Which means creating another useEffect with extra logic and to be triggered when that variable is updated.
import React, { useState, useEffect } from 'react';
import './GuessPicture.css';
import questions from './data/questions.js';
function GuessPicture() {
const [currentQuestion, setCurrentQuestion] = useState({});
const [unansweredQuestions, setUnansweredQuestions] = useState([]);
const [answeredQuestions, setAnsweredQuestions] = useState([]);
const [questionLoaded, setQuestionLoaded] = useState(false);
const [gameOver, setGameOver] = useState(false);
useEffect(() => {
setUnansweredQuestions(questions);
selectRandomQuestion();
setQuestionLoaded(true);
}, []);
useEffect(() => {
if (unansweredQuestions.length > 0) nextQuestion();
else alert('game over');
}, [unansweredQuestions]);
function selectRandomQuestion() {
const index = Math.floor(Math.random() * questions.length);
let selectedQuestion = questions[index];
selectedQuestion.options = shuffle(selectedQuestion.options);
setCurrentQuestion(selectedQuestion);
}
function nextQuestion() {
const index = Math.floor(Math.random() * unansweredQuestions.length);
let selectedQuestion = unansweredQuestions[index];
selectedQuestion.options = shuffle(selectedQuestion.options);
setCurrentQuestion(selectedQuestion);
}
// Fisher Yates Shuffle Algorithm
function shuffle(array) {
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
const onClickOption = (event) => {
if (currentQuestion.correctValue == event.target.dataset.value) {
setAnsweredQuestions((answeredQuestions) => [
...answeredQuestions,
currentQuestion,
]);
const newUnansweredQuestions = unansweredQuestions.filter(
(item) => item.id != currentQuestion.id
);
setUnansweredQuestions(newUnansweredQuestions);
} else alert('Wrong');
};
return (
<>
{questionLoaded || gameOver ? (
<div className="guess-picture-container">
<div className="guess-picture">
<img src={currentQuestion.image} alt="English 4 Fun" />
</div>
<div className="guess-picture-answers-grid">
{currentQuestion.options.map((key) => (
<button
key={key.value}
onClick={onClickOption}
data-value={key.value}
>
{key.display}
</button>
))}
</div>
</div>
) : null}
</>
);
}
export default GuessPicture;
Your code looks fine to me, however I would avoid the ternary operator in this situation and use the short-circuit operator instead. This makes your code a little cleaner.
instead of:
{questionLoaded || gameOver ? <SomeComponentHere /> : null}
try using:
{(questionLoaded || gameOver) && <SomeComponentHere />}
Why avoid the ternary operator? Because it's not a ternary operation, it is a boolean check. This makes your code more semantically appropriate.

Leafet setLatLng unmounts and mounts markers continuously, preventing events from firing

I'm trying to visualise 500+ vehicles using leaflet. When the position of a marker (vehicle) changes, it will move slowly to reach the destination (using requestAnimationFrame and leaflet's 'native' setLatLng since I don't want to update the state directly). It works well, but I also have a click listener on each marker and notice that it never fires. I soon realised that leaflet has been updating the marker continuously (the DOM element keeps blinking in the inspector). I attempted to log something to see if the component actually re-renders, but it doesn't. Seems like leaflet is messing with the DOM element under the hood.
const Marker = React.memo(function Marker({ plate, coors, prevCoors }) {
const markerRef = React.useRef();
const [activeVehicle, handleActiveVehicleUpdate] = useActiveVehicle();
const heading = prevCoors != null ? GeoHelpers.computeHeading(prevCoors, coors) : 0;
React.useEffect(() => {
if (prevCoors == null) return;
const [prevLat, prevLong] = prevCoors;
const [lat, long] = coors;
let animationStartTime;
const animateMarker = timestamp => {
if (animationStartTime == null) animationStartTime = timestamp;
const progress = (timestamp - animationStartTime) / 5000;
if (progress > 1) return;
const currLat = prevLat + (lat - prevLat) * progress;
const currLong = prevLong + (long - prevLong) * progress;
const position = new LatLng(currLat, currLong);
markerRef.current.leafletElement.setLatLng(position);
requestAnimationFrame(animateMarker);
};
const animationFrame = requestAnimationFrame(animateMarker);
// eslint-disable-next-line consistent-return
return () => cancelAnimationFrame(animationFrame);
}, [coors, prevCoors]);
React.useEffect(() => {
if (plate === '60C23403') console.log('re-render!');
// eslint-disable-next-line
});
return (
<LeafletMarker
icon={createIcon(plate === activeVehicle, heading)}
position={prevCoors != null ? prevCoors : coors}
onClick={handleActiveVehicleUpdate(plate, coors)}
ref={markerRef}
>
<Tooltip>{plate}</Tooltip>
</LeafletMarker>
);
});
How do I prevent this behaviour from leaflet? Any idea is appreciated. Thanks in advance :)

Resources