How can i stop my array from shuffling onClick - reactjs

So i'm building a Quiz App using Next Js and everything was going fine until i find out my array keeps Re-shuffling onClick of any button and i'm stumped on how to stop it.....I have tried adding e.preventDefault on all the button functions but nothing is working, So anytime i click an option it reshuffles the array thereby causing newArray[0] to always get the wrong answers, Any ideas on how i can stop reshuffling onClick??, Thanks in Advance
import axios from "axios";
import Axios from "axios";
import React, { useContext, useEffect, useState } from "react";
import styles from "../../styles/Questions.module.css";
import Login from "../Login";
import arrayShuffle from "array-shuffle";
import { AppContext } from "../helpers/helpers";
import Link from "next/link";
import Solutions from "../solutions";
export default function quiz({ questions }) {
const link = `https://the-trivia-api.com/api/questions`;
const [number, setNumber] = useState(0);
const [quizData, setQuizData] = useState(questions);
const [state, setState] = useState();
const [image, setImage] = useState();
const [numbering, setNumbering] = useState(1);
const [initialRenderComplete, setInitialRenderComplete] = useState(false);
const [chosenOption, setChosenOption] = useState("");
const [showAnswer, setShowAnswer] = useState(false);
const { score, setScore } = useContext(AppContext);
useEffect(() => {
if (localStorage) {
const getNameState = localStorage.getItem("name");
setState(getNameState);
} else {
console.log("errr");
}
}, []);
useEffect(() => {
if (localStorage) {
const getPhotoState = localStorage.getItem("photoUrl");
setImage(getPhotoState);
} else {
console.log("err");
}
}, []);
// Assigning incorrect answer array to oldArray
const oldArray = [
...quizData[number].incorrectAnswers,
quizData[number].correctAnswer,
];
const showAns = (e) => {
e.preventDefault();
setShowAnswer(!showAnswer);
};
// Pushing the correct answer into the incorrect answer array and forming a new array
oldArray.push(quizData[number].correctAnswer);
const newArray = [...new Set(oldArray)];
// Increase the number & index onClick of Next button
const increase = (e) => {
e.preventDefault();
setNumber(number + 1);
setNumbering(numbering + 1);
// checking if correct Answer is in the array then assigning it to getAns
const getAns = newArray.find(
(element) => element === quizData[number].correctAnswer
);
// If the getAns == Whatever You chose increase the score
if (getAns == chosenOption) {
setScore(score + 1);
}
};
// Only for the Finish Quiz Button
const finish = () => {
const getAns = newArray.find(
(element) => element === quizData[number].correctAnswer
);
if (getAns == chosenOption) {
setScore(score + 1);
}
};
const decrease = () => {
setNumber(number - 1);
setNumbering(numbering - 1);
};
// UseEffect to check against Hydration - Important!
useEffect(() => {
setInitialRenderComplete(true);
}, []);
const array = (e) => {
e.preventDefault();
setChosenOption(newArray[1]);
};
if (!initialRenderComplete) {
return null;
} else {
newArray.sort(() => (Math.random() > 0.5 ? 1 : -1)); // Shuffling the New array we pushed the
return (
<div className={styles.container}>
<div className={styles.profile}>
<div className={styles.mainProfile}>
<div className={styles.profilePic}>
<img src={image} alt="img" />
</div>
<h2>{state} </h2>
</div>
</div>
<div className={styles.main}>
<h2>
{numbering}. {questions[number].question}{" "}
</h2>
<div className={styles.list}>
<ul className={styles.list1}>
<div className={styles.flex}>
<h3>A. </h3>
<button onClick={array}>
<li>{newArray[1]} </li>
</button>
</div>
<div className={styles.flex}>
<h3>B. </h3>
<button onClick={() => setChosenOption(newArray[2])}>
{" "}
<li> {newArray[0]} </li>
</button>
</div>
</ul>
<ul className={styles.list2}>
<div className={styles.flexOption}>
<h2>C. </h2>
<button onClick={() => setChosenOption(newArray[0])}>
<li>{newArray[3]}</li>
</button>
</div>
<div className={styles.flexOption}>
<h2>D. </h2>
<button onClick={() => setChosenOption(newArray[3])}>
<li>{newArray[2]} </li>
</button>
</div>
</ul>
</div>
<div className={styles.btnStyle}>
<button onClick={decrease} className={styles.prev}>
Previous{" "}
</button>
{numbering == quizData.length ? (
<Link href="./scores">
<button className={styles.next} onClick={finish}>
{" "}
Finish Quiz{" "}
</button>
</Link>
) : (
<button onClick={increase} className={styles.next}>
Next{" "}
</button>
)}
{showAnswer ? (
<button onClick={showAns} className={styles.next}>
Hide Answer{" "}
</button>
) : (
<button onClick={showAns} className={styles.next}>
See Answer{" "}
</button>
)}
</div>
<p> {showAnswer ? quizData[number].correctAnswer : ""}</p>
</div>
</div>
);
}
}
export const getStaticProps = async () => {
const data = await Axios.get("https://the-trivia-api.com/api/questions");
// const data = req;
const initialData = data.data;
return {
props: {
questions: initialData,
},
};
};
And anytime i console.log the answer i pick, i don't get that option i clicked, So how can i extract the option i clicked on?

const arrayZero = (e) => {
e.preventDefault();
setChosenOption(newArray[0]);
};
const arrayOne = (e) => {
e.preventDefault();
setChosenOption(newArray[1]);
};
return(
//arrayZero being the function
<button onClick={arrayZero}>
<li>{newArray[0]} </li>
</button>
)
e.preventDefault() actually worked, Though it did not stop my array from reshuffling when i click on a button but it made my "chosenOption" state stay the same without changing.

Related

Menu won't open on click (ReactJS)

I'm trying to close the open menu once an outside click is triggered. I managed to implement it on a single button but now as I want to map a list of buttons, I can't manage to open any of the button menus.
import "./navbar.css";
import React, { useEffect, useRef, useState } from "react";
import { MenuItems } from "./menuItems";
export const Navbar = () => {
const [toggle, setToggle] = useState(true);
const btnRef = useRef();
const handleClick = () => {
setToggle(!toggle);
};
useEffect(() => {
const closeDropdown = (e) => {
if (e.path[0] !== btnRef.current) {
setToggle(false);
}
};
document.body.addEventListener("click", closeDropdown);
return () => {
document.body.removeEventListener("click", closeDropdown);
};
}, []);
return (
<div>
<div className="menu1">
<i className="fa fa-home" id="home"></i>
{MenuItems.map((n, i) => (
<li key={i} className="list">
<button ref={btnRef} onClick={handleClick} className="btn1">
{n.name}
<i className="fa fa-caret-down"></i>
</button>
</li>
))}
</div>
<div className={toggle ? "d-active" : "d-inactive"}>
<div className="dropdown">Empty</div>
</div>
</div>
);
};
My approach to this would be to have an OutsideDetector wrapper, with outsideClickHandler.
OutsideClickWatcher.js
const OutsideClickWatcher = (props) => {
const { onClickOutside } = props;
const wrapperRef = useRef(null);
useEffect(() => {
function handleClickOutside(event) {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
onClickOutside();
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [wrapperRef, onClickOutside]);
return <div ref={wrapperRef}>{props.children}</div>;
};
Navbar.js
export const Navbar = () => {
const [toggle, setToggle] = useState(true);
const btnRef = useRef();
const handleClick = () => {
setToggle(!toggle);
};
return (
<div>
<div className="menu1">
<i className="fa fa-home" id="home"></i>
{MenuItems.map((n, i) => (
<OutsideClickWatcher key={i} onClickOutside={() => setToggle(false)}>
<li className="list">
<button ref={btnRef} onClick={handleClick} className="btn1">
{n.name}
<i className="fa fa-caret-down"></i>
</button>
</li>
</OutsideClickWatcher>
))}
</div>
<div className={toggle ? "d-active" : "d-inactive"}>
<div className="dropdown">Empty</div>
</div>
</div>
);
};
Why do I prefer this approach? It gives me the flexibility to pass custom outside click handlers, and it's very reusable for all sorts of ui elements.
Note that this is a boilerplate code, it can obviously be improved further.

React app with api. Delete item for list doesn't work

When I click on the Delete button, my code does not work. There could be a problem in the function handleRemove.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
// API endPoint - Punk API
const API_URL = 'https://api.punkapi.com/v2/beers'
const List = () => {
const [drinks, setDrinks] = useState([])
const [searchTerm, setSearchTerm] = useState('')
const fetchData = async () => {
const { data } = await axios.get(API_URL)
setDrinks(data)
}
useEffect(() => {
fetchData()
}, [])
const handleRemove = (id) => {
let groupd = drinks
const newList = groupd.filter(group => group.id !== id)
setDrinks(newList)
}
return (
<div>
<div className="wrapper">
<div className="search__main">
<input type='text' placeholder="search..." onChange={e => {setSearchTerm(e.target.value)}}/>
</div>
</div>
<div className="wrapper">
<div className="search__box">
{drinks.filter((val) => {
if(searchTerm === ""){
return val
} else if(val.name.toLowerCase().includes(searchTerm.toLowerCase()) || val.description.toLowerCase().includes(searchTerm.toLowerCase())){
return val
}
}).map((drink, key) => {
return(
<div key={key} className="search__mini__box">
<div >
<img src={drink.image_url} alt="drink" className="search__img"/>
</div>
<h4>{drink.name}</h4>
<p>{drink.description}</p>
<button type="button" onClick={handleRemove(drink.id)}>
delete
</button>
</div>
)
})}
</div>
</div>
</div>
)
}
export default List
Since your handleRemove function call is within a return statement, you need to call the function like so:
onClick={() => handleRemove(drink.id)}
What happens is, the function is called immediately on render if done the way you've proposed in your question. We want the function to be called only when the button is clicked.

Update one element of big list without re render others elements in react hooks?

i want to optimize my react App by testing with a large list of li
Its a simple todo List.
By exemple, when click on a li, task will be line-through, and check icon will be green. This simple action is very slow with a large list because, the whole list is re render.
How to do this with React Hooks?
function App() {
const [list, setList] = useState([]);
const [input, setInput] = useState("");
const inputRef = useRef(null);
useEffect(() => inputRef.current.focus(), []);
//Pseudo Big List
useEffect(() => {
const test = [];
let done = false;
for (let i = 0; i < 5000; i++) {
test.push({ task: i, done });
done = !done;
}
setList(test);
}, []);
const handlerSubmit = (e) => {
e.preventDefault();
const newTask = { task: input, done: false };
const copy = [...list, newTask];
setList(copy);
setInput("");
};
const checkHandler = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy[index].done = !copy[index].done;
setList(copy);
};
const suppression = (e, index) => {
e.stopPropagation();
const copy = [...list];
copy.splice(index, 1);
setList(copy);
};
const DisplayList = () => {
return (
<ul>
{list.map((task, index) => (
<Li
key={index}
task={task}
index={index}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
);
};
//JSX
return (
<div className='App'>
<h1>TODO JS-REACT</h1>
<form id='form' onSubmit={handlerSubmit}>
<input
type='text'
placeholder='Add task'
required
onChange={(e) => setInput(e.target.value)}
value={input}
ref={inputRef}
/>
<button type='submit'>
<i className='fas fa-plus'></i>
</button>
</form>
{list.length === 0 && <div id='noTask'>No tasks...</div>}
<DisplayList />
</div>
);
}
export default App;
Li component
import React from "react";
export default function Li(props) {
return (
<li
onClick={(e) => props.checkHandler(e, props.index)}
className={props.task.done ? "line-through" : undefined}
>
{props.task.task}
<span className='actions'>
<i className={`fas fa-check-circle ${props.task.done && "green"}`}></i>
<i
className='fas fa-times'
onClick={(e) => props.suppression(e, props.index)}
></i>
</span>
</li>
);
}
CodeSandbox here: https://codesandbox.io/s/sad-babbage-kp3md?file=/src/App.js
I had the same question, as #Dvir Hazout answered, I followed this article and made your code the changes you need:
function App() {
const [list, setList] = useState([]);
const { register, handleSubmit, reset } = useForm();
//Pseudo Big List
useEffect(() => {
const arr = [];
let done = false;
for (let i = 0; i < 20; i++) {
arr.push({ id: uuidv4(), task: randomWords(), done });
done = !done;
}
setList(arr);
}, []);
const submit = ({ inputTask }) => {
const newTask = { task: inputTask, done: false, id: uuidv4() };
setList([newTask, ...list]);
reset(); //clear input
};
const checkHandler = useCallback((id) => {
setList((list) =>
list.map((li) => (li.id !== id ? li : { ...li, done: !li.done }))
);
}, []);
const suppression = useCallback((id) => {
setList((list) => list.filter((li) => li.id !== id));
}, []);
//JSX
return (
<div className="App">
<h1>TODO JS-REACT</h1>
<form onSubmit={handleSubmit(submit)}>
<input type="text" {...register("inputTask", { required: true })} />
<button type="submit">
<i className="fas fa-plus"></i>
</button>
</form>
{list.length === 0 && <div id="noTask">No tasks...</div>}
<ul>
{list.map((task, index) => (
<Li
key={task.id}
task={task}
suppression={suppression}
checkHandler={checkHandler}
/>
))}
</ul>
</div>
);
}
Li component
import React, { memo } from "react";
const Li = memo(({ task, suppression, checkHandler }) => {
// console.log each time a Li component re-rendered
console.log(`li ${task.id} rendered.`);
return (
<li
onClick={(e) => checkHandler(task.id)}
className={task.done ? "line-through" : undefined}
>
{task.task}
<span className="actions">
<i className={`fas fa-check-circle ${task.done && "green"}`}></i>
<i className="fas fa-times" onClick={(e) => suppression(task.id)}></i>
</span>
</li>
);
});
export default Li;
You can check it live here
I know it's probably late for your question, but may help others ;)
You can use React.memo and wrap the Li component. This will cache the instances of the Li component based on shallow comparison. Read more in the docs
Otherwise, if you don't need the state in the container, you can keep it locally in the Li component and then it won't cause a whole list rerender.

While on click on the span, how can we get the correct span name by using useRef()

I am getting the nominee_name as the last span name even after clicking on the right span element. How can I get the correct span name from here <span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>.
The above span is iterated based on the data received.
import React, { useRef, useEffect, useState } from "react";
import Axios from "axios";
const Dashboard = props => {
const [nominationCount, setNominationCount] = useState([]);
const [nameText, setNameText] = useState("");
let nominee_name = useRef(null);
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
useEffect(() => {
const fetchData = async () => {
try {
const res = await Axios.get('http://localhost:8000/service/nominationcount');
if (isMounted.current) {
setNominationCount(res.data);
console.log("Nomination count data from server :" + res.data);
}
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
const handleClick = () => {
setNameText(nominee_name.current.outerText);
}
return (
<div className="space_1 tile">
<h3>Nominations Count</h3>
<div className="grid-container">
{
!nominationCount.length && (<div className="nonominationdata">No nominations count to display !</div>)
}
{
nominationCount.map(data => (
<div key={data.id}>
<div onClick={() => {setOpen(!open); }} className="count badge" >
<span className="badgenumber" value={data.count} key={data.count}>{data.EmailCount}</span>
<span className="countname" key={data.nomineename} ref={nominee_name} onClick={handleClick}>{data.nomineename}</span>
</div>
</div>
))
}
</div>
</div>
<Modal
open={open}
onClose={() => {
setOpen(false);
}}
className={classes.modal}>
<form className={classes.form}>
<label className={classes.label}>Confirm winner {nameText}</label>
<input className={classes.submit} type="submit" value="Confirm" />
</form>
</Modal>
)
}
not sure you want to use ref here.
Just pass the name into your click handler:
(
<span className="countname" key={data.nomineename}
onClick={()=>setNameText(data.nomineename)}>{data.nomineename}</span>
)

React: How to update state for just one element, rather than batch update

I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!

Resources