How can I conditionally disable an input selection using a prop? - javascript

When a restart button is clicked on my quiz, I want the input selections to be removed. I tried testing disabled={restart === true} in my input selection on the question component, but it's not exactly working correctly. Any clue how to accomplish this? https://replit.com/#arshia93/Quizzical#sections/Question.jsx
Question component
export default function Question({question, answers, correctAnswer, updateAnswers, restart}) {
return (
<>
<h3 className="questions">
{`${decode(question)}`}
</h3>
{answers.map(( answerOption, index ) => (
<div key={index}>
<input
disabled={restart === true}
type="radio"
name={`answer option-${question}`}
id={`answer-options-${answerOption}`}
value={answerOption}
onChange={updateAnswers}
/>
<label
className={`answer-options ${correctAnswer ? 'answer-correct' : ``}`}
htmlFor={`answer-options-${question}`}>
{`${decode(answerOption)}`}</label>
</div>
))}
</>
)
}
Quiz data component
export default function QuizData() {
const [finished, setFinished] = React.useState(false)
const [newQuiz, setNewQuiz] = React.useState(false)
const [quizData, setQuizData] = React.useState([{
question: "",
answers: "",
correctAnswer: ""
}]);
const [selections, setSelections] = React.useState([]);
const [score, setScore] = React.useState(0)
React.useEffect(() => {
fetch("https://opentdb.com/api.php?amount=5&category=12&difficulty=medium&type=multiple")
.then(res => res.json())
.then(data => setQuizData(data.results.map(item => ({
question: item.question,
answers: arrayShuffle(item.incorrect_answers.concat(item.correct_answer)),
correctAnswer: item.correct_answer
}))))
},[newQuiz])
function handleSelectedAnswer(event) {
const {value} = event.target
setSelections(prevData => prevData.length > 0 ? [...prevData, value] : [value])
}
const allCorrectAnswers = quizData.map(item => item.correctAnswer)
const totalScore = selections.reduce((score, selection) =>
score + (allCorrectAnswers.includes(selection) ? 1 : 0) , 0);
function complete() {
setScore(totalScore)
setFinished(true)
}
function restart() {
setFinished(false)
setNewQuiz(prevState => !prevState)
}
return (
<div>
{ quizData.length > 1 ?
(quizData.map((item, index) => (
<div>
<Question
key={index}
question={item.question}
answers={item.answers}
restart={newQuiz}
correctAnswer={finished === true ? item.correctAnswer : ""}
chosenAnswer={selections.selectedAnswer}
updateAnswers={handleSelectedAnswer}
/>
</div>
)))
: <p>Loading...</p>
}
{finished === false ?
<button
className="quiz-button"
onClick={complete}>
Check answers
</button>
:
<>
{`You scored ${score}/5 correct`}
<button
className="quiz-button"
onClick={restart}>
Play again
</button>
</>
}
</div>
)
}`

Closed the earlier thread, because might be missleading.
In fact (wtihtout the simplifications and clearing) all you need to do is just remove the
value={answerOption}
disabled={restart === true}
line from Question.jsx (line 18 and 15).
You dont need to change the value dynamically here, since you always get new questions (thus no answers to display at the start) + no reason to disable it
add the key attribute for proper updates for the Question.jsx (Line 14)
key={answerOption}

Related

Make other block disappear when chose a value

How can I make other filter button disappear when picked 1 value.
Here is my code base:
const FilterBlock = props => {
const {
filterApi,
filterState,
filterFrontendInput,
group,
items,
name,
onApply,
initialOpen
} = props;
const { formatMessage } = useIntl();
const talonProps = useFilterBlock({
filterState,
items,
initialOpen
});
const { handleClick, isExpanded } = talonProps;
const classStyle = useStyle(defaultClasses, props.classes);
const ref = useRef(null);
useEffect(() => {
const handleClickOutside = event => {
if (ref.current && !ref.current.contains(event.target)) {
isExpanded && handleClick();
}
};
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, [isExpanded]);
const list = isExpanded ? (
<Form>
<FilterList
filterApi={filterApi}
filterState={filterState}
name={name}
filterFrontendInput={filterFrontendInput}
group={group}
items={items}
onApply={onApply}
/>
</Form>
) : null;
return (
<div
data-cy="FilterBlock-root"
aria-label={itemAriaLabel}
ref={ref}
>
<Menu.Button
data-cy="FilterBlock-triggerButton"
type="button"
onClick={handleClick}
aria-label={toggleItemOptionsAriaLabel}
>
<div>
<span>
{name}
</span>
<svg
width="8"
height="5"
viewBox="0 0 8 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.97291 0.193232C7.20854"
fill="currentColor"
/>
</svg>
</div>
</Menu.Button>
<div>
<div>
{list}
</div>
</div>
</div>
);
};
I am trying to achieve when I chose 1 value inside filter block the other block will disappear. Anyone have idea how can I work on this?
I am using React and Redux for this project
Thank you for helping me on this!!!!
Update:
Added parent component for FilterBlock.ks:
const FilterSidebar = props => {
const { filters, filterCountToOpen } = props;
const talonProps = useFilterSidebar({ filters });
const {
filterApi,
filterItems,
filterNames,
filterFrontendInput,
filterState,
handleApply,
handleReset
} = talonProps;
const filterRef = useRef();
const classStyle = useStyle(defaultClasses, props.classes);
const handleApplyFilter = useCallback(
(...args) => {
const filterElement = filterRef.current;
if (
filterElement &&
typeof filterElement.getBoundingClientRect === 'function'
) {
const filterTop = filterElement.getBoundingClientRect().top;
const windowScrollY =
window.scrollY + filterTop - SCROLL_OFFSET;
window.scrollTo(0, windowScrollY);
}
handleApply(...args);
},
[handleApply, filterRef]
);
const [selectedBlock, setSelectedBlock] = useState();
const filtersList = useMemo(
() =>
Array.from(filterItems, ([group, items], iteration) => {
const blockState = filterState.get(group);
const groupName = filterNames.get(group);
const frontendInput = filterFrontendInput.get(group);
if (selectedBlock) {
return (
<FilterBlock
key={group}
filterApi={filterApi}
filterState={blockState}
filterFrontendInput={frontendInput}
group={group}
items={items}
name={groupName}
onApply={handleApplyFilter}
initialOpen={iteration < filterCountToOpen}
iteration={iteration}
id={selectedBlock}
onSelected={setSelectedBlock}
/>
);
}
return (
<FilterBlock
key={group}
filterApi={filterApi}
filterState={blockState}
filterFrontendInput={frontendInput}
group={group}
items={items}
name={groupName}
onApply={handleApplyFilter}
initialOpen={iteration < filterCountToOpen}
iteration={iteration}
id={selectedBlock}
onSelected={setSelectedBlock}
/>
);
}),
[
filterApi,
filterItems,
filterNames,
filterFrontendInput,
filterState,
filterCountToOpen,
handleApplyFilter
]
);
return (
<div className="container px-4 mx-auto">
<Menu
as="div"
className="my-16 justify-center flex flex-wrap py-5 border-y border-black border-opacity-5"
>
{filtersList}
</Menu>
</div>
);
};
console.log(filterItems) and it gave me this output:
Map(3) {'markforged_printer_type' => Array(3),
'markforged_material_filter' => Array(7), 'markforged_parts_filter' =>
Array(7)} [[Entries]] 0 : {"markforged_printer_type" => Array(3)} 1 :
{"markforged_material_filter" => Array(7)} 2 :
{"markforged_parts_filter" => Array(7)}
Updated Answer
From the changes you provided, you are using useMemo() and useCallback(). Those kinds of optimizations in general are not necessary to be made or even decrease performance in some cases. Check this article from Kent C. Dodds (others can be easily found about the theme) to explain some issues with it.
About the changes, as a suggestion, you could use the .map()/.filter() functions instead Array.from().
You are splitting logic about rendering different components with the useMemo(), and this could be changed into one component instead of this whole logic inside the Parent component. (For my suggestion this will be not the case)
As a guide to your code, you could use something like this:
const FilterSidebar = ({ filters, filterCountToOpen }) => {
// here you have the state to control if there is a block selected
const [selectedGroup, setSelectedGroup] = useState();
const {
// only those are needed for this example
filterItems,
handleApplyFilter
} = useFilterSidebar({ filters });
return (
<div className="container px-4 mx-auto">
<Menu
as="div"
className="my-16 justify-center flex flex-wrap py-5 border-y border-black border-opacity-5"
>
{filterItems.map(([group, items], iteration) => {
const groupName = filterNames.get(group);
if (selectedGroup !== null && selectedGroup !== groupName) {
// returning null here should not render anything for this list item
return null;
}
return (
<FilterBlock
// pass all your extra props here
// but the important one is the `onApply`
onApply={(...args) => {
setSelectedGroup((prev) => prev !== null ? null : groupName);
return handleApplyFilter(...args);
}}
/>
);
}}
</Menu>
</div>
);
};
If you see any null on your screen, you could use first the .filter() and then the .map() or combine both with a single .reduce(). It should be something like this:
{filterItems
.filter(([group, items]) => selectedGroup === null || selectedGroup === filterNames.get(group))
.map(([group, items], iteration) => {
const groupName = filterNames.get(group);
return (
<FilterBlock
// pass all your extra props here
// but the important one is the `onApply`
onApply={(...args) => {
setSelectedGroup((prev) => prev !== null ? null : groupName);
return handleApplyFilter(...args);
}}
/>
);
}}
With your update, it is possible to see that you can select by the group (instead of the block which it was called before). Also, you can just add a little change to your onApply prop and that will save and re-render the list. If the selectedGroup is already there, removing the filter will show the other sections. Eventually, you'll need to trim this logic to accommodate other things such as selecting more than one filter and checking for that and so on.
Original Answer
From what you described I'm assuming what you want is: You have 3 FilterBlocks on your screen. Once a user selects one checkbox inside one opened "select" (that you are calling FilterBlock), you want the other FilterBlocks disappear from the screen and just the single FilterBlock with the selected option to stay at the screen (the other 2 will be hidden).
If that's your case, there are some possible options to achieve that but the easiest one is controlling this on a Parent Component: You can pass a prop from the parent component named something like onSelected, give an id to each FilterBlock, and when one filter is selected inside, you trigger that callback with the id from that FilterBlock.
const Parent = () => {
const [selectedBlock, setSelectedBlock] = useState();
if (selectedBlock) {
return <FilterBlock id={selectedBlock} onSelected={setSelectedBlock} />
}
return (
<>
<FilterBlock id="filter-block-1" onSelected={setSelectedBlock} />
<FilterBlock id="filter-block-2" onSelected={setSelectedBlock} />
<FilterBlock id="filter-block-2" onSelected={setSelectedBlock} />
</>
)
}
const FilterBlock = ({ id, onSelected }) => (
return (
<>
<button onClick={() => onSelected(id)}>Select filter block {id}</button>
<button onClick={() => onSelected()}>Unselect filter block {id}</button>
</>
);

Trying to mutate the state of React component

I'm trying to create a quizz app and got stuck at the moment when I need to change the background of the Answers (button) when the user clicked on it. Well, function holdAnswer does console.log the id of the answer, but doesn't change its background. What's missing here?
I assume I have also stored all answers that the user chose in some array in order to count how many answers the user guessed.
After I check if the answers are correct or not they have to be highlighted accordingly (correct/incorrect), so again it needs to mutate the state.
Is the code missing something from the beginning?
here is CodeSandBox link
App.js
import { useState } from "react";
import QuestionSet from "./QuestionSet";
import Answers from "./Answers";
import { nanoid } from "nanoid";
function App() {
const [isQuesLoaded, setIsQuesLoaded] = useState(false);
const [questions, setQuestions] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
async function startQuiz() {
try {
setIsQuesLoaded(!isQuesLoaded);
const response = await fetch(
"https://opentdb.com/api.php?amount=5&category=12&difficulty=easy&type=multiple"
);
const data = await response.json();
const allQuestions = data.results;
const listOfQuestions = allQuestions.map((item) => {
const allAnswers = [
{
id: nanoid(),
isCorrect: false,
isChosen: false,
answer: item.incorrect_answers[0],
},
{
id: nanoid(),
isCorrect: false,
isChosen: false,
answer: item.incorrect_answers[1],
},
{
id: nanoid(),
isCorrect: false,
isChosen: false,
answer: item.incorrect_answers[2],
},
{
id: nanoid(),
isCorrect: true,
isChosen: false,
answer: item.correct_answer,
},
];
return {
id: nanoid(),
question: item.question,
answers: allAnswers,
};
});
setQuestions(listOfQuestions);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
function holdAnswer(id) {
console.log(id);
setQuestions((prevQuestion) =>
prevQuestion.map((question) =>
question.answers.id === id
? {
...question,
answers: question.answers.map((answer) =>
answer.id === id
? { ...answer, isChosen: !answer.isChosen }
: answer
),
}
: question
)
);
}
const questionElm = questions.map((question, index) => {
return (
<section key={index}>
<QuestionSet question={question.question} key={question.id} />
<Answers
answers={question.answers}
isChosen={question.answers.isChosen}
holdAnswer={holdAnswer}
/>
</section>
);
});
return (
<div className="App">
{!isQuesLoaded ? (
<main>
<h1 className="title-app">Quizzical</h1>
<p className="desc-app">Some description if needed</p>
<button className="btn" onClick={startQuiz}>
Start Quiz
</button>
</main>
) : (
<main className="quest-box">
{loading && <div>Loading data...</div>}
{error && <div>{`There is a problem fetchning data = ${error}`}</div>}
<section className="quest-content">{questionElm}</section>
<button className="answer-btn">Check Answers</button>
</main>
)}
</div>
);
}
export default App;
Answers.js
export default function Answers(props) {
const styles = {
backgroundColor: props.answers.isChosen ? "#D6DBF5" : "transparent",
};
return (
<section className="answer-container">
<div
className="answer-div"
style={styles}
id={props.answers[3].id}
onClick={() => props.holdAnswer(props.answers[3].id)}
>
<p>{props.answers[3].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[1].id}
onClick={() => props.holdAnswer(props.answers[1].id)}
>
<p>{props.answers[1].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[2].id}
onClick={() => props.holdAnswer(props.answers[2].id)}
>
<p>{props.answers[2].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[0].id}
onClick={() => props.holdAnswer(props.answers[0].id)}
>
<p>{props.answers[0].answer}</p>
</div>
</section>
);
}
Your making confusions on object fields, using typescript will prevent you from doing this kind of error.
function holdAnswer(id) {
console.log(id);
setQuestions((prevQuestion) =>
prevQuestion.map((question) =>
question.answers.id === id // question.answers is an array
// you need to pass the questionId to holdAnswer and use it here
? {
...question,
answers: question.answers.map((answer) =>
answer.id === id
? { ...answer, isChosen: !answer.isChosen }
: answer
)
}
: question
)
);
}
// props.answers is also an array, create a component for Answer and use style here
const styles = {
backgroundColor: props.answers.isChosen ? "#D6DBF5" : "transparent"
};
Here is the codesandbox if you want to see how to fix it.
I wont give any direct answer, its up to you to find out.
First, there is an issue with the React.StrictMode as it runs some of the react api twice such as useEffect (and in ur case the setter for setQuestions), you can remove it for now since i dont think you really need it for this.
Lastly, if you look carefully at where you are changing the styles conditionally you'll see that you are referencing some object fields incorrectly.
it looks like you're just starting out react, so good luck and happy coding.

How to get answers from multiple components in ReactJS?

I'm working on a survey project, where I'm having a react component 'Questions', a child component 'QuestionOptions' with multiple children 'Radio', 'TextBox', 'Checkbox'.
Some questions in the database have options (multiple choice).
I want to find a way to gather all the answers answered by the user and send them to the database.
This is the Questions component:
const Questions = () => {
const [questions, setQuestions] = useState([])
var game_id = localStorage.getItem('game_id')
useEffect(() => {
axios
.post("http://127.0.0.1:8000/api/v1/questions/get_questions", {
game_id: game_id
})
.then((response) => {
const s = response.data.questions;
setQuestions(s);
});
}, []);
return (
<div>
<ul>
{questions.map((question) => (
<div key={question.id}>
<li key={question.id}>{question.question} ({question.points} points)</li>
<QuestionOptions question_id={question.id} question_type={question.question_type_id}/>
</div>
))}
</ul>
<Button text={"Submit"} id={"submit_btn"}/>
</div>
);
};
This is the QuestionOptions component:
const QuestionOptions = (props) => {
const [options, setOptions] = useState([]);
const question_type = props.question_type;
useEffect(() => {
axios
.post("http://127.0.0.1:8000/api/v1/question_options/get_options", {
question_id: props.question_id,
})
.then((response) => {
setOptions(response.data["options"]);
});
}, [props.question_id]);
if(question_type === 0){
return(
<TextBox />
)
}else if(question_type === 1){
return(
<Radio options={options}/>
)
}else if(question_type === 2){
return(
<Checkbox options={options}/>
)
}
};
The TextBox, Radio and Checkbox components looks as follows:
import React from "react";
const TextBox = () => {
return <input type={"text"} />;
};
export default TextBox;
So I want to get all the answers answered by the user, and disable the submit button if not all questions are answered.
The Questions component looks as follows:
What is your name?
.(input goes here)
What is your favorite color?
. option1
. option2
. option3
What is the best way to make that happen?
Here is example with two types of questions: ones with a save button option, others which update answers directly. Check the output of console.log after you click save button or type answer in the third question.
let questions = [
{ q: 'How old are you?', id: 0 },
{ q: 'How many pets do you have?', id: 1 },
{ q: 'Where were you born?', id: 2, type: 'NoSaveBtn' },
];
let Question = (props) => {
let [value, setValue] = React.useState('');
return (
<div>
<div>{props.q}</div>
<input value={value} onChange={(e) => setValue(e.target.value)}></input>
<button
onClick={() => {
props.onSaveAnswer(value);
}}
>
save answer
</button>
</div>
);
};
let QuestionNoSaveBtn = (props) => {
return (
<div>
<div>{props.q}</div>
<input
value={props.value || ''}
onChange={(e) => props.onSaveAnswer(e.target.value)}
></input>
</div>
);
};
export default function App() {
let [answers, setAnswers] = React.useState({});
console.log(answers);
return (
<div>
{questions.map((x) =>
x.type !== 'NoSaveBtn' ? (
<Question
q={x.q}
key={x.id}
onSaveAnswer={(answer) =>
setAnswers({ ...answers, [x.id]: answer })
}
/>
) : (
<QuestionNoSaveBtn
q={x.q}
key={x.id}
value={answers[x.id]}
onSaveAnswer={(answer) =>
setAnswers({ ...answers, [x.id]: answer })
}
/>
)
)}
</div>
);
}

React/Redux Changing background colour of button in nested array using state

I've been scratching my head over this for ages now.
I am trying to change the background colour of a specific button that is in a nested array.
I have an array of names in pairs that I loop over twice using a map, once to get the pair and once again to get the value. I output and assign the values to a button for each and am displaying the pairs together (E.g. each pair is indexed 0 and 1).
When I click on the button I wish to change only the background colour of the selected button. Currently all the buttons change colour. The issue being is that the state of the buttons effects all of them when I use a boolean to define the selection.
The handler I am using to do this also adds the value of the button to an array to be passed into global state later on as well.
Any help with this would be greatly greatly appreciated as I can't seem to find a way past it. Thanks!
import React, { Component } from "react";
import "../../App.scss";
import { Link } from "react-router-dom";
import Button from "../Button/Button";
class Matches extends Component {
constructor(props) {
super(props);
this.state = {
champ: [],
winningPlayers: [],
selected: false,
};
this.handleAddWinners = this.handleAddWinners.bind(this);
this.handleRound = this.handleRound.bind(this);
}
// Adds winners to a local array which is then sent
// to the global array using the handleNextRound action.
handleAddWinners = (e) => {
const winner = e.target.value;
const { champ } = this.state;
const { round } = this.props;
if (round !== 3) {
this.setState({
selected: !false,
winningPlayers: [...this.state.winningPlayers, winner],
});
} else {
this.setState({ champ: [...champ, winner] });
}
};
handleRound = () => {
const { round, handleNextRound, handleChampion } = this.props;
round !== 3 ? handleNextRound(this.state) : handleChampion(this.state);
this.setState({ winningPlayers: [] });
};
render() {
const { pairs, round, handleClear, roundWinners, champion } = this.props;
const { winningPlayers, selected, champ } = this.state;
const semi = roundWinners[0];
const final = roundWinners[1];
const champName = champion.map((item) => item);
const reset =
round !== 4 ? "block__reset__tournament" : "block__reset__new-game";
const newGame = `${round !== 4 ? "Reset" : "New Game?"}`;
const buttonClick = `${selected ? "selected" : "block__player"}`;
return (
<>
<div classname="container__wrapper">
<div className="container__tournament">
{round === 1 ? (
<section className="block__round ">
{pairs.map((item, index) => (
<div className="pairs" key={index}>
{item.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e)}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 2 ? (
<section className="block__round ">
{semi.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 3 ? (
<section className="block__round ">
{final.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : (
<section className="block__champion">
<p className="champion__greeting">
Congratulations
<br />
<span className="champion__name">{champName}!</span>
<br /> You've won the whole shebang!
</p>
</section>
)}
<Button
buttonClass={`${
round !== 4 ? "block__next-round" : "button__notActive"
}`}
label={`${round !== 3 ? "Next Round" : "See Winner"}`}
handleClick={this.handleRound}
disabled={disabled}
/>
<Link to={"/"} className={reset}>
<Button
buttonClass={reset}
handleClick={handleClear}
label={newGame}
/>
</Link>
</div>
</div>
</>
);
}
}
export default Matches;
This is the component that is handling most of this.
First I would like to say that you should always avoid from using array's index as keys. That is, unless your array is always at the same size and order.
Having said that - what you want to do is to know which button was selected - right?
So you need to store the last button that was selected. Because you don't use any ids anywhere, you can use the index of the pair and the index of the button to know which button was clicked.
Here's an example - I've changed only the round1 and the state code.
import React, { Component } from "react";
import "../../App.scss";
import { Link } from "react-router-dom";
import Button from "../Button/Button";
class Matches extends Component {
constructor(props) {
super(props);
this.state = {
champ: [],
winningPlayers: [],
selected: null,
};
this.handleAddWinners = this.handleAddWinners.bind(this);
this.handleRound = this.handleRound.bind(this);
}
// Adds winners to a local array which is then sent
// to the global array using the handleNextRound action.
handleAddWinners = (e, pairIndex, itemIndex) => {
const winner = e.target.value;
const { champ } = this.state;
const { round } = this.props;
if (round !== 3) {
this.setState({
selected: `${pairIndex}-${itemIndex}`,
winningPlayers: [...this.state.winningPlayers, winner],
});
} else {
this.setState({ champ: [...champ, winner] });
}
};
handleRound = () => {
const { round, handleNextRound, handleChampion } = this.props;
round !== 3 ? handleNextRound(this.state) : handleChampion(this.state);
this.setState({ winningPlayers: [] });
};
render() {
const { pairs, round, handleClear, roundWinners, champion } = this.props;
const { winningPlayers, selected, champ } = this.state;
const semi = roundWinners[0];
const final = roundWinners[1];
const champName = champion.map((item) => item);
const reset =
round !== 4 ? "block__reset__tournament" : "block__reset__new-game";
const newGame = `${round !== 4 ? "Reset" : "New Game?"}`;
const buttonClick = `${selected ? "selected" : "block__player"}`;
return (
<>
<div classname="container__wrapper">
<div className="container__tournament">
{round === 1 ? (
<section className="block__round ">
{pairs.map((item, pairIndex) => (
<div className="pairs" key={pairIndex}>
{item.map((names, itemIndex) => (
<Button
key={itemIndex}
handleClick={(e) => this.handleAddWinners(e, pairIndex, itemIndex)}
label={names}
buttonClass={`${pairIndex}-${itemIndex}` === selected ? '<enterYourBackgroundClass' : buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 2 ? (
<section className="block__round ">
{semi.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 3 ? (
<section className="block__round ">
{final.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : (
<section className="block__champion">
<p className="champion__greeting">
Congratulations
<br />
<span className="champion__name">{champName}!</span>
<br /> You've won the whole shebang!
</p>
</section>
)}
<Button
buttonClass={`${
round !== 4 ? "block__next-round" : "button__notActive"
}`}
label={`${round !== 3 ? "Next Round" : "See Winner"}`}
handleClick={this.handleRound}
disabled={disabled}
/>
<Link to={"/"} className={reset}>
<Button
buttonClass={reset}
handleClick={handleClear}
label={newGame}
/>
</Link>
</div>
</div>
</>
);
}
}
export default Matches;

How to set max number of items that can be selected in react-select?

I am using CreatableSelect component from react-select. Now users can select as many items as they want, but I want users to select no more than 5 items. How to limit max number of options that can be selected?
<CreatableSelect
classes={classes}
styles={selectStyles}
textFieldProps={{
label: "Tags"
}}
options={suggestions}
components={components}
value={this.state.multi}
onChange={this.handleChange("multi")}
placeholder=""
isMulti
/>
I recommend you to use a combination of custom component Menu and isValidNewOption like the following code:
// For this example the limite will be 5
const Menu = props => {
const optionSelectedLength = props.getValue().length || 0;
return (
<components.Menu {...props}>
{optionSelectedLength < 5 ? (
props.children
) : (
<div>Max limit achieved</div>
)}
</components.Menu>
);
};
function App() {
const isValidNewOption = (inputValue, selectValue) =>
inputValue.length > 0 && selectValue.length < 5;
return (
<div className="App">
<Creatable
components={{ Menu }}
isMulti
isValidNewOption={isValidNewOption}
options={options}
/>
</div>
);
}
Here a live example.
The idea is to prevent user to access the options after the limit X (5 in the example) and also to prevent the enter keyboard event on create through isValidNewOption prop.
a very easy way to do this is:
<Select
value={tags}
onChange={(v) => v.length < 4 ? setTags(v): null}
isMulti
name='tags'
options={options}
className='basic-multi-select'
classNamePrefix='select'
/>
just add a simple ternary check for how many items you wants
<CreatableSelect
classes={classes}
styles={selectStyles}
options={this.state.multi.length > 4 ? this.state.multi : suggestions}
components={Components}
value={this.state.multi}
placeholder="Tags"
onChange={(values) => this.setState({ multi: values })}
isValidNewOption={isValidNewOption} //Look at Marked Answer
isMulti
/>
Main documentation on how to resolve this issue can be found here:
https://github.com/JedWatson/react-select/issues/1341
const MultiCreatable = ({ options, handleChange, handleCreate, value, maxOptions }) => {
return (
<CreatableSelect
isMulti
placeholder={placeholder}
onChange={handleChange}
options={value.length === maxOptions ? [] : options}
noOptionsMessage={() => {
return value.length === maxOptions ? 'You have reached the max options value' : 'No options available' ;
}}
onCreateOption={handleCreate}
value={value}
/>
)
}
I am sharing my complete working component I think it can help>>
import React, { useState } from 'react';
import Select from 'react-select';
import makeAnimated from 'react-select/animated';
const animatedComponents = makeAnimated();
const ReactSelect = ({ data }) => {
const maxOptions = 5;
const [selectedOption, setSelectedOption] = useState([]);
const handleTypeSelect = e => {
setSelectedOption(e);
};
return (
<Select
onChange={handleTypeSelect}
getOptionLabel={x => x.name}
getOptionValue={x => x.slug}
components={animatedComponents}
isMulti
options={selectedOption.length === maxOptions ? [] : data}
noOptionsMessage={() => {
return selectedOption.length === maxOptions
? 'You have reached the max options value'
: 'No options available';
}}
label='tags'
/>
);
};
export default ReactSelect;
I found the more simple and clean way, without extra manipulations.
This way based on disabling an input component of 'react-select'.
Take a closer look at the parameter inputProps.
It can look like:
import Select from 'react-select';
import useField from 'client/components/hooks/useField';
const MultiSelect = ({
async,
creatable,
maxItems,
...restProps,
}) => {
const selectProps = {
...restProps,
// "inputProps: {disabled: boolean}" - our goal
...(typeof maxItems === 'number' && maxItems === restProps.value?.length ? {inputProps: {disabled: true}} : {})
};
const creatableTag = async ? Select.CreatableAsync : Select.Creatable;
const SelectTag = creatable ? creatableTag : selectTag;
return (
<div>
<SelectTag {...selectProps} />
</div>
);
};
const SomeComponentWithMultiSelect = () => {
const field = useField('data.name'); // field contains: {value: string[], ...}
const items = [
{
label: 'firstValue',
value: 1,
},
{
label: 'secondValue',
value: 2,
},
];
return (
<MultiSelect
items={items}
{...field}
creatable
maxItems={1} // {1} as our limit
/>
)
};
export default SomeComponentWithMultiSelect;
So you don't need to manage excess components.
For my case I used normal Select Component from react-select.
<Select
options={industries}
value={industry}
getOptionLabel={ x => x.id}
getOptionValue={ x => x.industry}
onChange={(e) => this.handleSelectChange(e, "industry")}
isMulti
/>
and handleSelectChange-
handleSelectChange = (e, name) => {
console.log(e)
if(e.length < 6){
return this.setState({
[name]: e
})
}
}
and state -
this.state = { industry: [] }

Categories

Resources