Making Answer button render with random indexes - javascript

Is there a way to make the correct button hard coded in the Question component render between the mapped incorrect buttons to prevent the correct answer from always being the first button rendered with every question? Or a way to map through both correct and incorrect answers to display the answers with random positions, the question array comes from the API in this form:
{
question: "The words and originate from the languages of which country?",
correctAnswer: "India",
incorrectAnswers: ["Papua New Guinea", "Ethiopia", "China"]
}
const [request, setRequest] = React.useState([])
React.useEffect (() => {
fetch('https://opentdb.com/api.php?amount=5')
.then(res => res.json())
.then(data => setRequest(data.results.map(({ question, correct_answer, incorrect_answers }) => ({question, correct_answer, incorrect_answers}))))
}, [])
console.log(request)
const questionElements = request.map(req => {
return (
<Question
question = {req.question}
correct_answer ={req.correct_answer}
incorrect_answers = {req.incorrect_answers}
scoreFunction = {scoreFunction}
// disabled = {disable}
/>
)
})
// Question Component
const incorrectAnswers = props.incorrect_answers.map(ans => {
return (
<button className ="button">{ans}</button>
)
})
return(
<div className = "question-div">
<h1 className = "question">{props.question}</h1>
<div className = "answerBlock">
<button
disabled = {disable}
className ="button correct"
onClick = {() => {
props.scoreFunction()
setDisable(true)}}>{props.correct_answer} </button>
{incorrectAnswers}
</div>
<hr />
</div>
)

You can put all answers to the same array, sort data inside it and then generate buttons.
This shows a simple shuffle function for arrays:
How to randomize (shuffle) a JavaScript array?
Example:
const correct_answer = 'correct'
const incorrect_answers = ['incorrect 1','incorrect 2','incorrect 3']
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
const all_answers = shuffle([correct_answer, ...incorrect_answers])
console.log(all_answers)
So your code should look like this:
const [request, setRequest] = React.useState([])
React.useEffect (() => {
fetch('https://opentdb.com/api.php?amount=5')
.then(res => res.json())
.then(data => setRequest(data.results.map(({ question, correct_answer, incorrect_answers }) => ({question, correct_answer, incorrect_answers, all_answers: [correct_answer, ...incorrect_answers]}))))
}, [])
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
console.log(request)
const questionElements = request.map(req => {
return (
<Question
question = {req.question}
correct_answer ={req.correct_answer}
incorrect_answers = {req.incorrect_answers}
scoreFunction = {scoreFunction}
// disabled = {disable}
/>
)
})
// Question Component
const allAnswers = shuffle(props.all_answers).map(ans => {
if(ans !== props.correct_answer) {
return (
<button className ="button">{ans}</button>
)
}
return (
<button
disabled = {disable}
className ="button correct"
onClick = {() => {
props.scoreFunction()
setDisable(true)}}
>{props.ans}</button>
)
})
return(
<div className = "question-div">
<h1 className = "question">{props.question}</h1>
<div className = "answerBlock">{allAnswers }</div>
<hr />
</div>
)

How about something like this?
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
const incorrect_answers_count = props.incorrect_answers.length;
const correct_answer_random_index = getRandomArbitrary(1, incorrect_answers_count) - 1;
const combined_answers = props.incorrect_answers.splice(correct_answer_random_index, 0, props.correct_answer);
const answers = combined_answers.map((ans, index) => {
return (
<button key={"answer-button-" + index} className ="button">{ans}</button>
)
})
Not sure why you want to have the correct button different, because the user can actually see this in the browser. But if you do, you can predefined the buttons and only then do the mapping.

Related

How can I make my delete function work in my todo app?

I am making a todo app. In the app, there are delete and edit functions. But the Delete function works at the first todo. If I try other todos, It deletes a lot of todos. I tried many ways but disn't work. Please help!
const todoForm = document.querySelector(".todo-form");
const todos = document.querySelector(".todos");
const todoTitle = document.querySelector(".todo-title");
const modal = document.querySelector("#modal");
const modalClose = document.querySelector(".close-button");
const editedText = document.querySelector(".edited-text ");
const submitEdit = document.querySelector(".submit");
const todoList = [];
let totalTodo = 0;
function findFromArray(id, array) {
for (let i = 0; i < array.length; i++) {
if (id === array[i].id) {
return array[i];
}
}
return null;
}
function deleteTodo(id) {
const element = document.getElementById(id);
element.remove();
const data = findFromArray(id, todoList);
todoList.splice(todoList.indexOf(data), 1);
}
function editTodo(id) {
const element = document.querySelector("#" + id + " h1");
modal.showModal();
modalClose.addEventListener("click", () => {
modal.close();
});
submitEdit.addEventListener("click", () => {
const newTodoText = editedText.value;
element.innerText = newTodoText;
modal.close();
});
}
todoForm.addEventListener("submit", (e) => {
e.preventDefault();
let title = todoTitle.value;
const newTodo = document.createElement("div");
const h1 = document.createElement("h1");
const deleteButton = document.createElement("button");
const editButton = document.createElement("button");
deleteButton.innerText = "Delete";
editButton.innerText = "Edit";
todoList.push({
id: "Todo" + totalTodo,
title,
});
totalTodo++;
for (let i = 0; i < todoList.length; i++) {
newTodo.classList.add("todo-card");
todos.appendChild(newTodo);
h1.innerText = todoList[i].title;
deleteButton.addEventListener("click", () => deleteTodo(todoList[i].id));
editButton.addEventListener("click", () => editTodo(todoList[i].id));
newTodo.appendChild(h1);
newTodo.appendChild(deleteButton);
newTodo.appendChild(editButton);
newTodo.id = todoList[i].id;
}
todoTitle.value = "";
});
<body>
<h1 class="todo-heading">Todo App</h1>
<form class="todo-form">
<input class="todo-title" type="text" placeholder="Todo Name" />
<input type="Submit" class="todo-button" />
</form>
<br />
<div class="todos"></div>
<dialog id="modal">
<h1>Edit</h1>
<input type="text" class="edited-text" placeholder="New Todo" />
<br />
<br />
<button class="submit">Submit</button>
<button class="close-button">
<svg class="svg-icon" viewBox="0 0 20 20">
<path
d="M10.185,1.417c-4.741,0-8.583,3.842-8.583,8.583c0,4.74,3.842,8.582,8.583,8.582S18.768,14.74,18.768,10C18.768,5.259,14.926,1.417,10.185,1.417 M10.185,17.68c-4.235,0-7.679-3.445-7.679-7.68c0-4.235,3.444-7.679,7.679-7.679S17.864,5.765,17.864,10C17.864,14.234,14.42,17.68,10.185,17.68 M10.824,10l2.842-2.844c0.178-0.176,0.178-0.46,0-0.637c-0.177-0.178-0.461-0.178-0.637,0l-2.844,2.841L7.341,6.52c-0.176-0.178-0.46-0.178-0.637,0c-0.178,0.176-0.178,0.461,0,0.637L9.546,10l-2.841,2.844c-0.178,0.176-0.178,0.461,0,0.637c0.178,0.178,0.459,0.178,0.637,0l2.844-2.841l2.844,2.841c0.178,0.178,0.459,0.178,0.637,0c0.178-0.176,0.178-0.461,0-0.637L10.824,10z"
></path>
</svg>
</button>
</dialog>
I was expecting to remove a todo element and the todo data. But It removes a lot of todo elements and data. I tried the splice method and the delete keyword but didn't work. Please help!
I think the problem is with the for loop you are using to create the new elements. The loop is iterating through the entire todoList array, but it should only be creating a new todo element for the last array item.
todoForm.addEventListener("submit", (e) => {
e.preventDefault();
let title = todoTitle.value;
const newTodo = document.createElement("div");
const h1 = document.createElement("h1");
const deleteButton = document.createElement("button");
const editButton = document.createElement("button");
deleteButton.innerText = "Delete";
editButton.innerText = "Edit";
todoList.push({
id: "Todo" + totalTodo,
title,
});
totalTodo++;
const lastTodo = todoList[todoList.length - 1];
newTodo.classList.add("todo-card");
todos.appendChild(newTodo);
h1.innerText = lastTodo.title;
deleteButton.addEventListener("click", () => deleteTodo(lastTodo.id));
editButton.addEventListener("click", () => editTodo(lastTodo.id));
newTodo.appendChild(h1);
newTodo.appendChild(deleteButton);
newTodo.appendChild(editButton);
newTodo.id = lastTodo.id;
todoTitle.value = "";
});
Also, like Jelmer commented.. In the deleteTodo function, remove todoList =. You should use the splice method directly on the todoList array to remove the element.
todoList.splice(todoList.indexOf(data), 1);

HTML elements becoming unused when I try to connect mongoDB with my JS file

I was working on a code where I fetch and display data from an API. I used to store it in Localstorage of browser's port.But now I wish to connect it to mongoDB.
When I import mongoclient my HTML elements become unused and their reference points nowhere.
This is the Script.js file
const searchInput = document.getElementById('searchInput')
const searchBtn = document.getElementById('searchBtn')
const moviesList = document.getElementById('moviesList')
const watchlist = document.getElementById('watchlist')
const removeWatchlistBtn = document.getElementsByClassName('remove-watchlist-btn')
const cardWatchlistBtn = document.getElementsByClassName('watchlist-btn')
const readMore = document.getElementsByClassName('read-more')
const readMorePlot = document.getElementsByClassName('read-more-plot')
const movieKey = document.getElementsByClassName('movie-key')
const localStorageKeys = Object.keys(localStorage)
if (searchBtn) {
searchBtn.addEventListener('click', searchMovies)
}
async function searchMovies() {
// Hide default elements
if (moviesList.children) {
let children = moviesList.children
let childrenArr = Array.prototype.slice.call(children)
childrenArr.forEach((child) => child.remove())
}
let res = await fetch(`https://www.omdbapi.com/?s=${searchInput.value.trim()}&apikey=e668e570`)
let data = await res.json()
const movies = data.Search
// Get and display search results
movies.forEach(async (movie) => {
let response = await fetch(`https://www.omdbapi.com/?i=${movie.imdbID}&apikey=e668e570`)
let moviesListData = await response.json()
console.log(moviesListData)
const readMoreMovieID = moviesListData.imdbID + 'more'
const hideReadMore = moviesListData.imdbID + 'hide'
const summaryPlot = `${moviesListData.Plot.substring(0, 110)}<span id=${hideReadMore}>...<button class="black read-more" onclick="showCompletePlot(${readMoreMovieID}, ${hideReadMore})">Read more</button></span>`
const readMorePlot = `<span class="read-more-plot" id=${readMoreMovieID} >${moviesListData.Plot.substring(110, moviesListData.Plot.length)}</span>`
const completePlot = moviesListData.Plot
const longPlot = summaryPlot + readMorePlot
const movieID = moviesListData.imdbID
const movieIDkey = moviesListData.imdbID + 'key'
const watchlistBtnKey = moviesListData.imdbID + 'watchlistBtn'
const removeBtnKey = moviesListData.imdbID + 'removeBtn'
moviesList.innerHTML += `
<div class="cards">
<div class="card" id=${movieID}>
<span id=${movieIDkey} class="hide movie-key">${movieIDkey}</span>
<img src=${moviesListData.Poster} class="card-poster" />
<div class="card-header">
<h2 class="card-title">${moviesListData.Title}</h2>
<img src="images/star-icon.svg" class="star-icon" />
<span class="card-rating">${moviesListData.imdbRating}</span>
</div>
<div class="card-meta">
<span class="card-runtime">${moviesListData.Runtime}</span>
<span>${moviesListData.Genre}</span>
<button class="card-btn card-watchlist watchlist-btn" id="${watchlistBtnKey}" onclick="addToWatchlist(${movieIDkey}, ${movieID}, ${watchlistBtnKey}, ${removeBtnKey})"><img src="images/watchlist-icon.svg" alt="Add film to watchlist" class="card-watchlist-plus-icon" /> Watchlist</button>
<button class="card-btn card-watchlist remove-watchlist-btn" id="${removeBtnKey}" onclick="removeFromWatchlist(${movieIDkey}, ${removeBtnKey}, ${watchlistBtnKey}, ${removeBtnKey})"><img src="images/remove-icon.svg" alt="Remove film to watchlist" class="card-watchlist-plus-icon" /> Remove</button>
</div>
<p class="card-plot">${completePlot.length < 110 ? completePlot : longPlot}</p>
</div>
</div>
`
displayWatchlistOrRemoveBtn()
})
}
function displayWatchlistOrRemoveBtn() {
for (let movie of movieKey) {
const removeBtnID = movie.id.slice(0, 9) + 'removeBtn'
const removeBtn = document.getElementById(removeBtnID)
const watchlistBtnID = movie.id.slice(0, 9) + 'watchlistBtn'
const watchlistBtn = document.getElementById(watchlistBtnID)
localStorageKeys.forEach((key) => {
if (movie.id === key) {
removeBtn.style.display = 'inline'
watchlistBtn.style.display = 'none'
}
})
}
}
function showCompletePlot(readMoreMovieID, hideReadMore) {
readMoreMovieID.style.display = 'inline'
hideReadMore.style.display = 'none'
}
async function addToWatchlist(movieIDkey, movieID, watchlistBtnKey, removeBtnKey) {
console.log(movieID.id)
const a = movieID.id
const b= movieID.Title
let sanket = await fetch(`https://www.omdbapi.com/?i=${movieID.id}&apikey=e668e570`)
const ab = await sanket.json()
console.log(ab)
localStorage.setItem(movieIDkey.innerHTML, movieID.innerHTML)
watchlistBtnKey.style.display = 'none'
removeBtnKey.style.display = 'inline'
}
function removeFromWatchlist(movieIDkey, watchlistBtnKey, removeBtnKey) {
localStorage.removeItem(movieIDkey.innerHTML)
// Get parent element (the movie card div) and remove it
if (watchlist) {
localStorage.removeItem(movieIDkey.innerHTML)
const parentEl = document.getElementById(movieIDkey.innerHTML).parentElement
parentEl.remove()
}
watchlistBtnKey.style.display = 'inline'
removeBtnKey.style.display = 'none'
// Display default elements if local storage empty
if (watchlist && localStorage.length === 0) {
if (watchlist.children) {
const children = watchlist.children
const childrenArr = Array.prototype.slice.call(children)
childrenArr.forEach((child) => (child.style.display = 'flex'))
}
}
}
// Hide default elements if data is in local storage
if (watchlist && localStorage.length > 0) {
if (watchlist.children) {
const children = watchlist.children
const childrenArr = Array.prototype.slice.call(children)
childrenArr.forEach((child) => (child.style.display = 'none'))
}
}
for (let i = 0; i < localStorage.length; i++) {
const getLocalStorage = localStorage.getItem(localStorage.key(i))
// Display every key's value to the watchlist
if (watchlist) {
watchlist.innerHTML += `<div class="card">${getLocalStorage}</div>`
// Hide the 'add to watchlist' button
for (let button of cardWatchlistBtn) {
button.style.display = 'none'
}
// Display the 'remove from watchlist' button
for (let button of removeWatchlistBtn) {
button.style.display = 'inline'
}
}
}
Here when I use const { MongoClient } = require("mongodb"); the elements readMore and readMorePlot (Line no.7 and 8) become unused.
I tried exporting my addToWatchlist function but problem persists.
I also have tried adding Type=Module in html file but that doesn't work.

React js: Uncaught Error: Objects are not valid as a React child [duplicate]

This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Closed 11 months ago.
I am trying to display my data from an array. The array comes from a reducer. I wanted to do it using the .map method but there is an error (at produit.map). I don't know why but it returns me "Uncaught Error: Objects are not valid as a React child". The console tell me too that they found Objects with keys. Can someone help me please?
const LastProduit = () => {
const dispatch = useDispatch();
const step = 3;
const [count, setCount] = useState(step);
useEffect(() => {
dispatch(getProduit());
}, [dispatch,]);
const { height, width } = useWindowDimensions();
const produits = useSelector((state) => state.produitReducer);
const produitsArray = produits;
const RealProduitArray = produitsArray[0];
console.log(produits);
console.log(RealProduitArray);
//console.log(RealProduitArray[0].titre);
const a = [produits];
const b = 1 + produits.length
for (let i = 0; i < b; i++) {
return a[i];
}
let screenwidth = { width };
let nbr = 0;
let nbrspace = 0;
let valeur = "true";
console.log(screenwidth.width);
if (screenwidth.width <= 768) { nbr = 2; nbrspace = -60; valeur = "false" }
else { nbr = 4; nbrspace = -170; }
return (
<div className='LastProduit'>
<p className='categories_titre'>Derniers Produits </p>
<div className='LastProduit_img'>
<Swiper modules={[Navigation, Pagination, Scrollbar, A11y]}
spaceBetween={nbrspace}
slidesPerView={nbr}
navigation
pagination={{ clickable: true }}
scrollbar={{ draggable: true }}
onSwiper={(swiper) => console.log(swiper)}
onSlideChange={() => console.log('slide change')}
>
{produits.map((produit) =>
<SwiperSlide>
<Produit produit={produit} key={produit._id} />
</SwiperSlide>
)}
</Swiper>
</div>
</div>
);
};
Your map function needs a return statement. In multi-line callback functions you have to include the return keyword.
Example:
{
produits.map((produit) => {
return (
<SwiperSlide>
<Produit produit={produit} key={produit._id} />
</SwiperSlide>
)
})
}

In Svelte, is there a way to undo invalid user input, without changing the state?

In Svelte, is there a way to undo invalid user input, without triggering a momentarily fake property change for state?
For example (try it online), when user types in 123a in the input box, I want to covert it to a number and assign it counter.
For that, I have to introduce a temporary and redundant change to counter (using Number.NaN below). Otherwise, counter won't change, and 123a will remain inside the input box, while I want to revert it back to 123:
<script>
let count = 0;
const handleClick = () => count++;
const handleChange = async e => {
const userValue = e.target.value;
let newValue = userValue? parseInt(userValue): 0;
if (isNaN(newValue)) newValue = count;
count = Number.NaN;
await Promise.resolve();
count = newValue;
};
</script>
Count: <button on:click={handleClick}>{count}</button>
<br />
A: <input value={count} on:input={handleChange} />
<br />
Is there a better way of doing it? Essentially, I want trigger a manual re-render for a part of my Svetle component, without changing the state.
Of course, in the event handler I could undo the change just by modifying e.target.value directly:
const handleChange = async e => {
const userValue = e.target.value;
let newValue = userValue? parseInt(userValue): 0;
if (isNaN(newValue)) newValue = count;
if (newValue == count)
e.target.value = count;
else
count = newValue;
};
But I wonder if there's a way to tell Svelte to re-render the whole <input>?
To compare, here's how I could do it in React (try it online):
function App() {
let [count, setCount] = useState(0);
const handleClick = () => setCount((count) => count + 1);
const handleChange = (e) => {
const userValue = e.target.value;
let newValue = userValue ? parseInt(userValue) : 0;
if (isNaN(newValue)) newValue = count;
setCount(newValue);
};
return (
<>
Count: <button onClick={handleClick}>{count}</button>
<br />
A: <input value={count} onInput={handleChange} />
<br />
</>
);
}
You simply need to bind count to the value of the input. REPL
<script>
let count = 0;
const handleClick = () => count++;
const handleChange = (e) => {
const userValue = e.target.value;
const newValue = userValue ? parseInt(userValue, 10) : 0;
count = isNaN(newValue) ? count : newValue;
};
</script>
Count: <button on:click={handleClick}>{count}</button>
<br />
A: <input bind:value={count} on:input={handleChange} />
<br />
Note: Remember to always pass a radix to parseInt(), it does not default to 10.

How to get button's index

I used to make bunch of buttons through for-looping.
title[] has a lot of values.
export const renderButtons1 = (numOfBtns,title,navigate) => {
const views1 = [];
for ( var i = 0; i < numOfBtns; i++) {
views1.push(
<Button
key={i}
title = {title[i]}
onPress={() => navigate('List', {
Title : title[i]
})}
color="#841584"
/>);
}
return views1;
}
in EventListscreen, Title: title[i] does not work, i value is not recognized
the one I want to ask is when I click first button, index 0 will be retrieved.
I thought I can use button key, but it is not fixed value - when I click first button at first and at second time, it is different..
I found the way,
you guys need to make the function for making buttons
so it looks like this
export const makingbutton = (i,ttl,navigate) => {
return <Button
key={i}
title = {ttl[i]}
onPress={() => navigate('List', {
Title : title[i]
})}
color="#841584"
/>
}
export const renderButtons1 = (numOfBtns,title,navigate) => {
const views1 = [];
for ( var i = 0; i < numOfBtns; i++) {
views1.push(
makingbutton(i,title,navigate)
);
}
return views1;
}

Categories

Resources