Issue with adding conditions to secret santa app - reactjs

I'm working on creating secret santa app for my christmas party.
For now it works, but I need to add some conditions.
function App() {
var names = ["John", "Martha", "Adam", "Jane", "Michael"];
const shuffle = (arr: string[]) => {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
};
const randomNames = shuffle(names);
const matches = randomNames.map((name, index) => {
return {
santa: name,
receiver: randomNames[index + 1] || randomNames[0],
};
});
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Secret santa game</p>
<select>
<option>Select your name...</option>
{names.map((name) => (
<option> {name}</option>
))}
</select>
<div>
{matches.map((match) => {
return (
<div>
{match.receiver},{match.santa}
<br />
</div>
);
})}
</div>
</header>
</div>
);
}
export default App;
John and Martha are couple so they will buy themselves presents outside of the party anyway, so if one of them is receiver and another is santa, I want to generate results again so they'll be assigned to someone else.
I'm not sure how I can accomplish this.

You could define your relationships like this:
const couples = { John: "Martha" };
then have a function that verifies that your conditions are met:
const conditions_check = (santa: string, receiver: string) => {
if (couples[santa] === receiver) {
return false;
} else if (couples[receiver] === santa) {
return false;
}
return true;
};
and then finally you can check if your conditions are met while generating your matches and shuffle your pool of names again if needed:
let matches = randomNames.map((name, index) => {
let receiver = randomNames[(index + 1) % randomNames.length];
for (let i = 0; i < 25; i++) {
if (conditions_check(name, receiver)) {
break;
}
randomNames = shuffle(names);
}
return {
santa: name,
receiver: receiver
};
});
You can check out the whole implementation in this playground.

Related

Am getting NaN when I try to get the overall sum (Reactjs)

I'm trying to make a function that shows the overall cost of tickets. Adult tickets are $25 each, and minor tickets are $10 each. I have a script that creates the counter, and another script to calculate the sum, but whenever I try to use it to count the overall cost (the second script), it doesn't work, I just get NaN. What am I doing wrong here? Console log doesn't say anything.
function TicketCounter() {
const { useState } = React;
const [counterAdult, setCounter] = useState(0);
const [counterMinor, counterSet] = useState(0);
let adultTickets = [];
let minorTickets = [];
const incrementCounterAdult = () => {
setCounter(counterAdult + 1);
adultTickets.push(counterAdult);
};
const decrementCounterAdult = () => {
if (counterAdult !== 0) {
setCounter(counterAdult - 1);
adultTickets.pop(counterAdult);
}
};
const incrementCounterMinor = () => {
counterSet(counterMinor + 1);
minorTickets.push(counterMinor);
};
const decrementCounterMinor = () => {
if (counterMinor !== 0) {
counterSet(counterMinor - 1);
minorTickets.pop(counterMinor);
}
};
return (
<div className="ticket-options">
<div className="option-adult">
<p>Adult Tickets (16yrs+)</p>
<div className="ticket-amount">
<img
src={'../images/arrowup.png'}
alt="arrow up"
className="arrow-up-adult"
onClick={incrementCounterAdult}
/>
<span className="number-adult">
{counterAdult}
</span>
<img
src={'../images/arrowdown.png'}
alt="arrow down"
className="arrow-down-adult"
onClick={decrementCounterAdult}
/>
</div>
</div>
<div className="option-minor">
<p>Minor Tickets (15yrs-)</p>
<div className="ticket-amount">
<img
src={'../images/arrowup.png'}
alt="arrow up"
className="arrow-up-minor"
onClick={incrementCounterMinor}
/>
<span className="number-minor">
{counterMinor}
</span>
<img
src={'../images/arrowdown.png'}
alt="arrow down"
className="arrow-down-minor"
onClick={decrementCounterMinor}
/>
</div>
</div>
</div>
)
}
ReactDOM.render(<TicketCounter />, document.querySelector(".ticket-counter"));
function TotalCost() {
let overallCost = 0;
let adultCost = () => {
adultTickets.forEach(element => {
adultCost = element * 25;
});
}
let minorCost = () => {
minorTickets.forEach(element => {
minorCost = element * 10;
});
}
overallCost = Number(adultCost) + Number(minorCost);
return(
<div className="option-cost">
<p>Ticket cost: $</p>
{overallCost}
</div>
)
}
ReactDOM.render(, document.querySelector('.cost'));
To get the overall cost you just need to multiply counterAdult and counterMinor and update the value when these counters change:
function TotalCost() {
const [overallCost, setOverallCost] = useState(0);
useEffect(() => {
const newOverall = counterAdult * 25 + counterMinor * 10;
setOverallCost(newOverall);
}, [counterAdult, counterMinor]);
return (
<div className='option-cost'>
<p>Ticket cost: $</p>
{overallCost}
</div>
);
}

How to load virtual dom once the data from api are loaded React

I think that my dom loads faster than data do.
I use useEffect to grab my data and store in localStorage
But when my data are loaded I have to refresh the page one more time to display my fetched data
How can I make my dom wait for the data. I tried to use useState and check whether we get the data from API, I put setLoadDom(true) in my useEffect if statement and then display dom if(loadDom) is true otherwise I show Loading...
From api I get
[
{firstName, lastName, id, completed},
....
{}
]
sortInOrder - get data and transforms it in array where indexes are [a,b,c,d,...] and the value is an array of all objects where lastName === to the letter. tmp['a'] = [{lastName: "Amanda"...},...]
toggleCheckbox - easy to understand, after I toggled checkbox I use localstorage to save my 'checked' status
Functional component
function App() {
const ALPHABET = "abcdefghijklmnopqrstuvwxyz".split("");
const [employeesData, setEmployeesData] =
useState(JSON.parse(localStorage.getItem('data')) ?? []);
const [alphabeticalOrder, setAlphabeticalOrder] = useState([]);
const sortInOrder = () => {
let tmp = []
for(let i = 0; i < ALPHABET.length; i++) {
tmp[ALPHABET[i]] = []
for(let j = 0; j < employeesData.length; j++) {
if(employeesData[j]['lastName'][0].toLowerCase() === ALPHABET[i]) {
tmp[ALPHABET[i]].push(employeesData[j])
}
}
}
for(let i = 0; i < ALPHABET.length; i++) {
if (tmp[ALPHABET[i]].length === 0) {
tmp[ALPHABET[i]] = ['-']
}
}
return tmp
}
const toggleCheckbox = (e, el) => {
let checked = e.target.checked;
setEmployeesData(
employeesData.map(person => {
if(person['id'] === el['id']) {
person['completed'] = checked;
}
return person;
})
)
localStorage.setItem('data', JSON.stringify(employeesData))
setAlphabeticalOrder(sortInOrder())
}
useEffect(() => {
axios.get('example.com')
.then(res => {
let data = JSON.parse(localStorage.getItem('data')) ?? []
if(res.data.length !== data.length) {
localStorage.setItem('data', JSON.stringify(res.data.map(el => ({...el, completed: false}))))
}
})
.catch(e => console.log('Error occured', e))
setAlphabeticalOrder(sortInOrder())
}, [])
return (
<div className="container">
<div className="wrapper">
<div className="employees">
<div className="employees_title align">Employees</div>
<div className="employees_info_block">
{ALPHABET.map((letter, i) => (
<div className="employees_block" key={letter}>
<div className="employee_letter" key={i}>{letter}</div>
{[...alphabeticalOrder[letter] ?? []].map(el => {
if (el[0] != '-') {
return (
<div className="info_field" key={el["id"]}>
<input type="checkbox" checked={el['completed'] ?? false}
onChange={(e) => toggleCheckbox(e, el)}/>
<div className="info_field_text">{el["firstName"]} {el["lastName"]}</div>
</div>
)
} else {
return (
<div>
-
</div>
)
}
})
}
<br/>
</div>
))}
</div>
</div>
<div className="birthday">
<div className="birthday_title align">Employes birthday</div>
</div>
</div>
</div>
);
}
could you try using setEmployeesData after:
localStorage.setItem('data', JSON.stringify(res.data.map(el => ({...el, completed: false}))))

Storing array objects using index on radio button click in react [duplicate]

This question already has answers here:
How to sort an array of integers correctly
(32 answers)
Closed 2 years ago.
How to store radio button values in ascending order of index in an array in react?
let questions;
class App extends Component {
constructor(props) {
super(props);
this.state = {
btnDisabled: true,
questions: [],
};
this.changeRadioHandler = this.changeRadioHandler.bind(this);
this.submitHandler = this.submitHandler.bind(this);
}
changeRadioHandler = (event) => {
const qn = event.target.name;
const id = event.target.value;
let text = this.props.data.matrix.options;
let userAnswer = [];
for (let j = 0; j < text.length; j++) {
userAnswer.push(false);
}
const option = text.map((t, index) => ({
text: t.text,
userAnswer: userAnswer[index],
}));
const elIndex = option.findIndex((element, i) => element.text === id);
let options = [...option];
options[elIndex] = {
...options[elIndex],
userAnswer: true,
};
const question = {
id: event.target.value,
qn,
options,
};
if (this.state.questions.some((question) => question.qn === qn)) {
questions = [
...this.state.questions.filter((question) => question.qn !== qn),
question,
];
} else {
questions = [...this.state.questions, question];
}
console.log(questions);
this.setState({ questions });
if (questions.length === text.length) {
this.setState({
btnDisabled: false,
});
}
};
submitHandler = () => {
console.log("btn clkd", questions);
};
render() {
return (
<div class="matrix-bd">
{this.props.data.header_text && (
<div class="header-qn">
<h5>{this.props.data.header_text} </h5>
</div>
)}
{this.props.data.matrix && (
<div class="grid">
{this.props.data.matrix.option_questions.map((questions, j) => {
return (
<div class="rows" key={j}>
<div class="cell main">{questions.text}</div>
{this.props.data.matrix.options.map((element, i) => {
return (
<div class="cell" key={i}>
<input
type="radio"
id={"rad" + j + i}
name={questions.text}
value={element.text}
onChange={this.changeRadioHandler}
></input>
<label htmlFor={"rad" + j + i}>{element.text}</label>
</div>
);
})}
</div>
);
})}
</div>
)}
<div class="buttonsubmit text-right">
<button
type="button"
id="QstnSubmit"
name="QstnSubmit"
class="btn btn-primary"
disabled={this.state.btnDisabled}
onClick={this.submitHandler}
>
{this.props.labels.LBLSUBMIT}
</button>
</div>
</div>
);
}
}
export default App;
I have added my code where the array is unordered with respect to index, I want to order the array using qn(const qn = event.target.name;) object. Like, how it came from the database, likewise in the same order, it should go into the db.
You can use the sort() method, then have a callback function with two arguments and return them in an accending order, like this:
ans.sort((a, b) =>{return a-b});

Randomizing quiz-answers fetched from a rest-API

So I've created a quiz-app that fetches 10 random questions with three incorrect and one correct answer to choose from. Everything is working great at the moment except that I can't get the answers to pop up randomly. And by that I mean that the correct answer is always at the bottom of the choices presented.
I know that the answer is Math.floor(Math.random() * ... ) but I honestly have no idea where to put it. I've tried everything. I could really use some help.
import React, { Component } from "react";
import "./App.css";
const API =
"https://opentdb.com/api.php?amount=10&category=20&difficulty=medium";
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
score: []
};
}
componentDidMount() {
this.populateAppWithData();
}
populateAppWithData() {
fetch(API)
.then(response => response.json())
.then(data => this.setState({ results: data.results }))
.catch(error => console.error(error))
}
render() {
const { results } = this.state;
return (
<div className="App">
<h1>Quiz App</h1>
<TheCounter results={results}
Counter={this.state.score}
/>
</div>
);
}
}
class MythologyAnswers extends Component {
constructor(props) {
super(props);
this.state = {
answered: "",
isRight: null,
};
}
answerClicked(answer) {
const { hasAnswered, correct_answer } = this.props;
return event => {
const isRight = correct_answer === answer;
hasAnswered(isRight);
this.setState({
answered: answer,
isRight,
});
};
}
render() {
const { question, correct_answer, incorrect_answers } = this.props;
const { answered, isRight } = this.state;
return (
<div className="allAnswers">
{question}
{incorrect_answers && incorrect_answers
.concat(correct_answer)
.map(answer => (<div onClick={this.answerClicked(answer)}>{answer} </div>))}<br />
{answered && `You answered ${answered}`} {" "} <br />
<div className="correctAnswer"> {" "}{answered && isRight && "This is correct!"} </div> <br />
<div className="incorrectAnswer"> {" "}{answered && !isRight && `This is incorrect. The correct answer is ${this.props.correct_answer}`} {" "}</div>
</div>
)
}
}
class TheCounter extends Component {
constructor(props) {
super(props);
this.state = {
right: 0,
Counter: 0,
unanswered: 0,
};
}
questionAnswered = isRight => {
this.setState(({ Counter, right }) => ({
Counter: Counter + 1,
right: right + isRight,
}));
}
render() {
const { results } = this.props;
const { Counter } = this.state;
const unanswered = this.props.results && Counter;
if (unanswered >= 10) {
return <div className="theUnanswered"> You got {this.state.right} right out of {this.state.Counter} </div>;
}
const question = results[Counter];
return (
<div className="newQuestion">
<MythologyAnswers {...question} hasAnswered={this.questionAnswered} />
</div>
)
}
}
export default App;
We just need to apply the randomizer in the right place. You use .concat() to combine the two arrays, so it makes sense to use the randomizer right after that and before we call .map()
I've set up something that preserves alot of the existing logic you already wrote.
This will help create the new Arr and set-up the markup for your component.
renderChoices = () => {
const { correct_answer, incorrect_answers } = this.props;
let allAnswers = incorrect_answers
? incorrect_answers.concat(correct_answer)
: [];
//simple shuffle algorithm. Just inject your array and it'll pop out a new one.
function createRandom(arr) {
let myArr = [...arr]; //copy arr we pass in
let randomizedArr = []; //gets popuated by loop
while (myArr.length > 0) {
var randomIndex = Math.floor(Math.random() * myArr.length); //create random number
randomizedArr.push(myArr[randomIndex]); //add choice randomly to arr
myArr.splice(randomIndex, 1); //cut out a piece of the array then resart loop
}
//when loop has finished, return random array
return randomizedArr;
}
//call randomizer and get new Arr
let randomizedArr = createRandom(allAnswers);
//use .map to create markup with randomizedArr
return randomizedArr.map(answer => {
return <div onClick={this.answerClicked(answer)}>{answer}</div>;
});
};
So if you were to call the above function inside render, it will create the answer-set for you.
render() {
const { question } = this.props;
const { answered, isRight } = this.state;
return (
<div className="allAnswers">
{question}
{ this.renderChoices()}
<br />
{answered && `You answered ${answered}`} <br />
<div className="correctAnswer">
{answered && isRight && "This is correct!"}
</div>
<br />
<div className="incorrectAnswer">
{answered &&
!isRight &&
`This is incorrect. The correct answer is ${
this.props.correct_answer
}`}
</div>
</div>
);
}
Clean and not too-complex :)
Edit: so without changing your original code too much:
createRandom(arr) {
let myArr = [...arr]; //copy arr we pass in
let randomizedArr = []; //gets popuated by loop
while (myArr.length > 0) {
var randomIndex = Math.floor(Math.random() * myArr.length); //create random number
randomizedArr.push(myArr[randomIndex]); //add choice randomly to arr
myArr.splice(randomIndex, 1); //cut out a piece of the array then resart loop
}
//when loop has finished, return random array
return randomizedArr;
}
render() {
const { question, correct_answer, incorrect_answers } = this.props;
const { answered, isRight } = this.state;
const allAnswers =
incorrect_answers ? incorrect_answers.concat(correct_answer) : [];
const randomizedAnswers = this.createRandom(allAnswers)
return (
<div className="allAnswers">
{question}
{randomizedAnswers
.map(answer => (
<div onClick={this.answerClicked(answer)}>{answer} </div>
))}
<br />
{answered && `You answered ${answered}`} <br />
<div className="correctAnswer">
{answered && isRight && "This is correct!"}
</div>
<br />
<div className="incorrectAnswer">
{answered &&
!isRight &&
`This is incorrect. The correct answer is ${
this.props.correct_answer
}`}
</div>
</div>
);
}
So in the edited version we do a couple things:
Defined a function called createRandom() ... all it does is
randomize your answers choices.
In render, we created a new variable called allAnswers which just
concat() the incorrect_answers and correct_answer like you did
previously. If there are no incorrect_answers defined, we will use
an empty array [] as the default value.
Create a new variable called randomizedAnswers. We call
createRandom() and pass in allAnswers as the argument. It returns
the randomized array for us to use.
Then just .map() over randomizedAnswers to create your
answer-choices markup.
In below code , I suppose you are trying to concat incorrect_answer array with the correct_answer
incorrect_answers && incorrect_answers
.concat(correct_answer)
so array becomes [incorrect_answers,incorrect_answers,incorrect_answers,correct_answer]
i.e correct answer is at end
so if you want to insert correct answer at random position and suppose there are 4 options then first generate a random number between 0 and 3
let randonIndex = Math.floor(Math.random() * 4)
then insert correct answer at random index
incorrect_answers && incorrect_answers
.splice(randomIndex, 0, correct_answer);
I'd personally add it right after fetching the data. Using an inline shuffle like Fisher-Yales you don't need to modifiy your structure at all.
const fisherYalesShuffle = (a) => {
let j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
}
However, since you generally want to move as much data-logic as possible to your backend, you should also consider shuffling them there instead.
You can define a shuffle function (This one is Fisher-Yates (aka Knuth) shuffle):
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;
}
And then just apply the function when setting the state like this:
this.setState({ results: this.shuffle(data.results) })

Rendering comma separated list of links

I'm trying to output a list of comma separated links and this is my solution.
var Item = React.createComponent({
render: function() {
var tags = [],
tag;
for (var i = 0, l = item.tags.length; i < l; i++) {
if (i === item.tags.length - 1) {
tag = <span><Tag key={i} tag={item.tags[i]} /></span>;
} else {
tag = <span><Tag key={i} tag={item.tags[i]} /><span>, </span></span>;
}
tags.push(tag);
}
return (
<tr>
<td>
{item.name}
</td>
<td>
{tags}
</td>
</tr>
);
}
});
I was just wondering if there was a better, more clean way to accomplish this?
Thanks
Simply
{tags.map((tag, i) => <span key={i}>
{i > 0 && ", "}
<Tag tag={tag} />
</span>)}
In React 16 it can be done even more simpler:
{tags.map((tag, i) => [
i > 0 && ", ",
<Tag key={i} tag={tag} />
])}
At Khan Academy we use a helper called intersperse for this:
/* intersperse: Return an array with the separator interspersed between
* each element of the input array.
*
* > _([1,2,3]).intersperse(0)
* [1,0,2,0,3]
*/
function intersperse(arr, sep) {
if (arr.length === 0) {
return [];
}
return arr.slice(1).reduce(function(xs, x, i) {
return xs.concat([sep, x]);
}, [arr[0]]);
}
which allows you to write code like:
var tags = item.tags.map(function(tag, i) {
return <Tag key={i} tag={item.tags[i]} />;
};
tags = intersperse(tags, ", ");
Or simply write the list items to an unordered list and use CSS.
var Item = React.createComponent({
render: function() {
var tags = this.props.item.tags.map(function(i, item) {
return <li><Tag key={i} tag={item} /></li>
});
return (
<tr>
<td>
{this.props.item.name}
</td>
<td>
<ul className="list--tags">
{tags}
</ul>
</td>
</tr>
);
}
});
And the CSS:
.list--tags {
padding-left: 0;
text-transform: capitalize;
}
.list--tags > li {
display: inline;
}
.list--tags > li:before {
content:',\0000a0'; /* Non-breaking space */
}
.list--tags > li:first-child:before {
content: normal;
}
import React from 'react';
import { compact } from 'lodash';
// Whatever you want to separate your items with commas, space, border...
const Separator = () => { ... };
// Helpful component to wrap items that should be separated
const WithSeparators = ({ children, ...props }) => {
// _.compact will remove falsey values: useful when doing conditional rendering
const array = compact(React.Children.toArray(children));
return array.map((childrenItem, i) => (
<React.Fragment key={`${i}`}>
{i > 0 && <Separator {...props} />}
{childrenItem}
</React.Fragment>
));
};
const MyPage = () => (
<WithSeparators/>
<div>First</div>
{second && (<div>Maybe second</div>)}
{third && (<div>Maybe third</div>)}
<div>Fourth</div>
</WithSeparators>
);
A function component that does the trick. Inspired by #imos's response. Works for React 16.
const Separate = ({ items, render, separator = ', ' }) =>
items.map((item, index) =>
[index > 0 && separator, render(item)]
)
<Separate
items={['Foo', 'Bar']}
render={item => <Tag tag={item} />}
/>
Here's a solution that allows <span>s and <br>s and junk as the separator:
const createFragment = require('react-addons-create-fragment');
function joinElements(arr,sep=<br/>) {
let frag = {};
for(let i=0,add=false;;++i) {
if(add) {
frag[`sep-${i}`] = sep;
}
if(i >= arr.length) {
break;
}
if(add = !!arr[i]) {
frag[`el-${i}`] = arr[i];
}
}
return createFragment(frag);
}
It filters out falsey array elements too. I used this for formatting addresses, where some address fields are not filled out.
It uses fragments to avoid the warnings about missing keys.
Simple one:
{items.map((item, index) => (
<span key={item.id}>
{item.id}
{index < items.length - 1 && ', '}
</span>
))}
To add to the great answers above Ramda has intersperse.
To comma separate a bunch of items you could do:
const makeLinks = (x: Result[]) =>
intersperse(<>,</>, map(makeLink, x))
Pretty succinct
The solution without extra tags
<p className="conceps inline list">
{lesson.concepts.flatMap((concept, i) =>
[concept, <span key={i} className="separator">•</span>]
, ).slice(-1)}
</p>
generates something like
Function • Function type • Higher-order function • Partial application
The easiest way to do
const elementsArr = ["a", "b", "c"];
let elementsToRender = [] ;
elementsArr.forEach((element, index) => {
let elementComponent = <TAG className="abc" key={element.id}>{element}</TAG>
elementsToRender.push(elementComponent);
if(index !== (elementsArr.length - 1)){
elementsToRender.push(", ");
}
});
render(){
return (
<div>{elementsToRender}</div>
)
}

Resources