I want to disable the button which i have clicked when i choose an option in my quiz and the rest of the quiz options are enabled ...any ideas on how to do it for these options ? i know we have to use disabled={pass a function} but don't know how to cooperate that with the options
This is my quiz.js:
render() {
const { userAns, options, scores, currentQuest } = this.state;
return (
<div>
<div>
<ProgressBar animated now={this.state.currentQuest * 10} />
</div>{" "}
{this.state.questions}
<br></br> {this.state.scores}
<br></br>
<p style={{ textAlign: "center" }}>Q{this.state.currentQuest}</p>
{this.state.pictures}
<br></br>
{options.map((option, id) => (
<Button
size="lg"
block
key={id}
className={`ui floating message options
${userAns === option ? "selected" : null}
`}
onClick={() => this.checkAns(option)}
>
{option}
</Button>
))}
<div className="hrLine"></div>
<br></br>
<Button onClick={() => this.checkAns()}>CHECK</Button>
{currentQuest < Quizdata.length - 1 && (
<Button
disabled={this.state.disabled}
onClick={() => {
this.nextQuestion();
}}
>
NEXT
</Button>
)}
<br></br>
{currentQuest === Quizdata.length - 1 && (
<Button
onClick={() => {
this.finishQuiz();
}}
>
Finish
</Button>
)}
</div>
);
}
You don't need to pass a function to the disabled attribute, this is a 'boolean` value, Now you have to keep track of the options that you've been clicked.
I'm assuming that you have an array of options so first of all you need to convert that to an object of objects so you can access any option with a unique key:
const options = {
0:{
isSelected: false,
value: 'your value 0',
},
1:{
isSelected: false,
value: 'your value 1',
},
2:{
isSelected: false,
value: 'your value 2',
};
}
This way you are able to track every options and the current state.
With this change your code has to change a bit here, you need to access the option by the key of the object this way options[optionKey].
I've used Object.keysthat creates an array of the keys of the object that you pass it, then you iterate through the array and map the key with the options object here is the docs that refer to this method in case you've never used it:
Object.keys(options).map((optionKey, id) => (
<Button
size="lg"
block
disabled={ options[optionKey].isSelected }
key={optionKey}
className={`ui floating message options
${userAns === options[optionKey] ? "selected" : null}
`}
onClick={() => this.checkAns(options[optionKey])}
>
{options[optionKey].value}
</Button>
))}
import React, { Component } from "react";
import { Quizdata } from "./questions";
//import { FaHorse, FaArrowR } from "react-icons/fa";
import { Button, ProgressBar } from "react-bootstrap";
class Quiz extends Component {
constructor(props) {
super(props);
this.state = {
isMounted: false,
disabled: false,
userAns: null,
options: {},
currentQuest: 0,
scores: 0,
pictures: "",
correct: false
};
}
componentDidMount() {
this.loadQuiz();
// console.log("Quiz1 loaded"); //loads quiz 1 data in
}
componentDidUpdate(prevProps, prevState) {
const { currentQuest } = this.state;
if (this.state.currentQuest !== prevState.currentQuest) {
this.setState({
disabled: true,
questions: Quizdata[currentQuest].question,
options: Quizdata[currentQuest].options,
answer: Quizdata[currentQuest].answer,
pictures: Quizdata[currentQuest].picture
});
}
}
loadQuiz = () => {
const { currentQuest } = this.state;
//console.log("QD", Quizdata);
this.setState(() => {
return {
isMounted: true,
questions: Quizdata[currentQuest].question,
options: Quizdata[currentQuest].options,
answer: Quizdata[currentQuest].answer,
pictures: Quizdata[currentQuest].picture
};
});
// console.log(this.state.answer);
};
nextQuestion = () => {
const { scores, answer, userAns } = this.state;
if (userAns === null) {
return;
}
console.log("scores " + this.state.scores);
this.setState({
currentQuest: this.state.currentQuest + 1
});
};
checkAns = answer => {
//userans and answer switched
let newAnswers = this.state.options;
// console.log("NewAnsw", newAnswers, answer);
Object.keys(newAnswers).map(optionKey => {
newAnswers[optionKey].isSelected = false;
});
newAnswers[answer].isSelected = true;
this.setState({
userAns: newAnswers.value,
// options: newAnswers,
disabled: false
});
console.log("user clicked " + this.state.userAns);
if (this.state.userAns === answer) {
console.log("Correct");
this.setState({
scores: this.state.scores + 1
// correct: false
});
} else {
console.log("wrong");
this.setState({ scores: this.state.scores });
}
};
finishQuiz = () => {
const { scores, userAns, answer } = this.state;
if (this.state.currentQuest === Quizdata.length - 1) {
if (userAns === answer) {
console.log("Correct");
this.setState({
scores: scores + 1
// correct: false
});
} else {
console.log("wrong");
this.setState({ scores: scores });
}
console.log("scores " + this.state.scores);
}
};
render() {
const { userAns, options, scores, currentQuest } = this.state;
// console.log("Opts", options);
if (!this.state.isMounted) {
return null;
}
return (
<div>
<div>
<ProgressBar animated now={this.state.currentQuest * 10} />
</div>{" "}
{this.state.questions}
<br /> {this.state.scores}
<br />
<p style={{ textAlign: "center" }}>Q{this.state.currentQuest}</p>
{this.state.pictures}
<br />
{/* {options.map((option, id) => (
<Button
size="lg"
block
key={id}
className={`ui floating message options
${userAns === option ? "selected" : null}
`}
onClick={() => this.checkAns(option)}
>
{option}
</Button>
))}
*/}
{Object.keys(options).map((optionKey, id) => (
<Button
size="lg"
block
disabled={options[optionKey].isSelected}
key={optionKey}
className={`ui floating message options
${userAns === options[optionKey] ? "selected" : null}
`}
onClick={() => this.checkAns(optionKey)}
>
{options[optionKey].value}
</Button>
))}
<div className="hrLine" />
<br />
<Button onClick={() => this.checkAns()}>CHECK</Button>
{currentQuest < Quizdata.length - 1 && (
<Button
disabled={this.state.disabled}
onClick={() => {
this.nextQuestion();
}}
>
NEXT
</Button>
)}
<br />
{currentQuest === Quizdata.length - 1 && (
<Button
onClick={() => {
this.finishQuiz();
// this.nextQuestion();
// this.pushtoDB();
// this.props.handleDisableValue(scores); // child to parent
}}
>
Finish
</Button>
)}
</div>
);
}
}
export default Quiz;
this is what i worked on i have all working but the user answer cant be used to check if its correct...i saw your updated sandbox i see how u minimized some functions but the score doesnt work either thats the issue im facing now and i want to console the user selected answer when i console i get numbers as its going through the index
Related
I am working on a quiz project. Particularly I am working on selecting an answer out of 4 options. Whenever I click on an option I am encountering an error which is given below
This is my App.js file
import { useEffect, useState } from "react";
import Quiz from "./components/Quiz";
import { nanoid } from "nanoid";
function App() {
const [showScreen, setShowScreen] = useState(false);
const [quizData, setQuizData] = useState([]);
useEffect(() => {
fetch("https://opentdb.com/api.php?amount=5&category=21&type=multiple")
.then((res) => res.json())
.then((data) =>
setQuizData(
data.results.map((question) => {
return {
...question,
id: nanoid(),
answers: shuffle([
...question.incorrect_answers,
question.correct_answer,
]),
correct_answer: question.correct_answer,
};
})
)
);
}, []);
function shuffle(arr) {
let array = arr.map((ans) => {
return {
id: nanoid(),
answer: ans,
isSelected: false,
};
});
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function handleSelect(id, selectedAnsId) {
console.log(id, selectedAnsId);
setQuizData((prevQuizData) => {
console.log(quizData);
prevQuizData.map((question) => {
return question.id === id
? {
...question,
answers: question.answers.map((answer) => {
return answer.id === selectedAnsId
? {
...answer,
isSelected: !answer.isSelected,
}
: { ...answer, isSelected: false };
}),
}
: question;
});
});
}
const newQuizData = quizData.map((question) => {
return (
<Quiz
key={question.id}
id={question.id}
question={question.question}
answers={question.answers}
handleSelect={handleSelect}
/>
);
});
function openSeparateScreen() {
setShowScreen((prevShowScreen) => !prevShowScreen);
}
return (
<div className="App">
{showScreen ? (
<div className="quiz-container">
{newQuizData}
<button
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
margin: "auto",
}}
className="btn"
>
Check answers
</button>
</div>
) : (
<div className="container">
<h1 className="heading">Quizzical</h1>
<p className="text">To start a quiz please click on the button</p>
<button className="btn" onClick={openSeparateScreen}>
Start Quiz
</button>
</div>
)}
</div>
);
}
export default App;
This is my Quiz.js file
import React from "react";
const Quiz = (props) => {
return (
<>
<div className="questions">
<h3>{props.question}</h3>
<div className="options">
{props.answers.map((answer) => {
return (
<h6
key={answer.id}
className={`${
answer.isSelected ? "isSelected" : "transparent"
}`}
onClick={() => props.handleSelect(props.id, answer.id)}
>
{answer.answer}{" "}
</h6>
);
})}
</div>
</div>
<hr />
</>
);
};
export default Quiz;
The error message shows I am getting the error at line 66 which starts from
const newQuizData = quizData.map((question) => {
return (
<Quiz
key={question.id}
id={question.id}
question={question.question}
answers={question.answers}
handleSelect={handleSelect}
/>
);
});
any kind of help would be much appreciated.
Your quizData is undefined at some point.
The most likely culprit is
setQuizData((prevQuizData) => {
console.log(quizData);
prevQuizData.map((question) => {
you don't return the value there; try to change it to return prevQuizData.map((question) => {
I have an array of job descriptions that I want to hide a part of each description and show it completely when a button is clicked using React hooks.
I am iterating over the array( consists of id and description) to show all the descriptions as a list in the component. There is a button right after each paragraph to show or hide the content.
readMore is used to hide/show the content and
activeIndex is used to keep track of clicked item index.
This is what I have done so far:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [readMore, setReadMore] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{readMore ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
setActiveIndex(index);
if (activeIndex === id) {
setReadMore(!readMore);
}
}}
>
{readMore ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
The problem is that when I click one button it toggles all the items in the list.
I want to show/hide content only when its own button clicked.
Can somebody tell me what I am doing wrong?
Thanks in advance.
Your readMore state is entirely redundant and is actually causing the issue. If you know the activeIndex, then you have all the info you need about what to show and not show!
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndex === index ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
if (activeIndex) {
setActiveIndex(null);
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Edit: The aforementioned solution only lets you open one item at a time. If you need multiple items, you need to maintain an accounting of all the indices that are active. I think a Set would be a perfect structure for this:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndices, setActiveIndices] = useState(new Set());
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndices.has(index) ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
const newIndices = new Set(activeIndices);
if (activeIndices.has(index)) {
newIndices.delete(index);
} else {
newIndices.add(index);
}
setActiveIndices(newIndices);
}}
>
{activeIndices.has(index) ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Try this
{readMore && (activeIndex === id) ? description : `${description.substring(0, 250)}...`}
function Destination() {
const travels = [
{
title: "Home"
},
{
title: "Traveltype",
subItems: ["Local", "National", "International"]
},
{
title: "Contact",
subItems: ["Phone", "Mail", "Chat"]
}
];
const [activeIndex, setActiveIndex] = useState(null);
return (
<div className="menu-wrapper">
{travels.map((item, index) => {
return (
<div key={`${item.title}`}>
{item.title}
{item.subItems && (
<button
onClick={() => {
if (activeIndex) {
if (activeIndex !== index) {
setActiveIndex(index);
} else {
setActiveIndex(null);
}
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? `Hide` : `Expand`}
</button>
)}
{activeIndex === index && (
<ul>
{item.subItems &&
item.subItems.map((subItem) => {
return (
<li
key={`li-${item.title}-${subItem}`}
>
{subItem}
</li>
);
})}
</ul>
)}
</div>
);
})}
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
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;
The code to get tables depends on how many items are selected in the left window in my image below but don't know What I need to write in the onClick function of the button to move the first selected item to the first table GMV1 and the second selected item to GMV2 and so on.
What I want to do is first check the status of contractLineItemSelectionChecked. If the status is true, check the selected items have different contract ids and push the different contract id objects to different places.
[
{
cNo: "CDH0000403",
contractLineItemSelectionChecked: true,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
},
{
cNo: "CDH0000404",
contractLineItemSelectionChecked: false,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
},
{
cNo: "CDH0000405",
contractLineItemSelectionChecked: true,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
}
];
<Field
name="transactionSelected"
component="select"
className="custom-select my-1 mr-sm-2"
id="moveContractLineItems"
onChange={e => {
setFieldValue("transactionSelected", "GMV+ /contract");
document.getElementById("creategoodsMovementsContractsTransaction").click();
getIndex();
}}
>
<option key="select" value="">
Select
</option>
<option key="GMV+ /contract" value="GMV+ /contract">
GMV+ /contract
</option>
<option key="PMT+/contract" value="PMT+/contract">
PMT+/Contract
</option>
</Field>;
<FieldArray name={`transactions["goodsMovements"].transactionItems`}>
{({ push }) => (
<input
type="button"
hidden
id="creategoodsMovementsContractsTransaction"
onClick={() => {
let myCounter = 0;
checkedCheckboxes.forEach(v =>
v.contractLineItemSelectionChecked ? myCounter++ : v
);
for (var i = 0; i < myCounter; i++) {
push({
name:
"GMV" +
(typeof values.transactions.goodsMovements.transactionItems !==
"undefined" &&
values.transactions.goodsMovements.transactionItems.length + 1),
transactionId: "",
transactionType: "1",
requestedPostingDate: null,
companyCode: "",
profitCenter: "",
attachments: [],
comments: "",
transactionLineItems: []
});
}
}}
/>
)}
</FieldArray>;
<button
type="button"
className="btn btn-primary my-1"
onClick={() => moveItems()}
>
Move
</button>;
Maybe the following will give you an idea how to do this:
const Item = React.memo(
//use React.memo to create pure component
function Item({ item, onChange, checked }) {
console.log('rendering:', item.id)
// can you gues why prop={new reference} is not a problem here
// this won't re render if props didn't
// change because it's a pure component
return (
<div>
<div>{item.name}</div>
<input
type="checkbox"
checked={checked}
onChange={() => onChange(item.id, !checked)}
/>
</div>
)
}
)
const SelectedItem = React.memo(
//use React.memo to create pure component
function SelectedItem({ item }) {
console.log('rendering selected:', item.id)
return (
<div>
<div>{item.name}</div>
</div>
)
}
)
class Parent extends React.PureComponent {
state = {
data: [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' }
],
checked: {}
}
toggle = (_id, add) => {
const newChecked = add
? {
...this.state.checked,
[_id]: true
}
: // cannot use Object.fromEntries because SO babel is too old
Object.entries(this.state.checked)
.filter(([key]) => Number(key) !== _id)
.reduce((result, [key, value]) => {
result[key] = value
return result
}, {})
this.setState({ checked: newChecked })
}
render() {
return (
<div>
<div>
<h4>items</h4>
{this.state.data.map(item => (
<Item
item={item}
onChange={this.toggle}
checked={Boolean(this.state.checked[item.id])}
key={item.id}
/>
))}
</div>
<div>
<h4>Selected items</h4>
{this.state.data
.filter(item => this.state.checked[item.id])
.map(item => (
<SelectedItem item={item} key={item.id} />
))}
</div>
</div>
)
}
}
//render app
ReactDOM.render(<Parent />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If i click on a particular ToDos edit button, its value should be defaulted inside the textarea but everytime the last ToDo is defaulting, can somebody please help, whether using ref is a right choice or something else, then where i m wrong what i'm suppose to do ?
handleEdit() {
e.preventDefault();
.....
}
renderDisplay() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
<div>
<button className="btn btn-primary btn-xs glyphicon glyphicon-pencil"
onClick={this.handleEdit.bind(this)}
/>
</div>
<hr/>
</li>
</div>
)})
}
</div>
);
}
renderForm() {
return(
<div>
<textarea className="form-control" defaultValue={this.refs.newText.innerText} rows="1" cols="100" style={{width: 500}}/>
</div>
)
}
render() {
if(this.state.editing) {
return this.renderForm();
}else {
return this.renderDisplay();
}
}
}
First of all you are using an old ref API. You should use this one, where you set the ref to the instance of the class using this with a callback.
<input ref={ref => {this.myInput = ref}} />
And then you can access its value by just referring to this.myInput .
As for your "bug", keep in mind that you are looping over and overriding the ref. so the last ref assignment would be the last item in the array.
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
There will always be 1 newText ref and it will always be the last item in the array.
You should render different ref names according to the item id and then pass the id of the item to the renderForm so it can access the relevant ref.
With that said, i really recommend to extract the todo to a different component as well as the form. I don't see a valid reason to use refs in this case.
Edit
As a follow-up to your comment, here is a small example of how you would use components instead of refs in order to get information from the child like values etc..
class Todo extends React.Component {
onClick = () => {
const { todoId, onClick } = this.props;
onClick(todoId);
}
render() {
const { value, complete } = this.props;
return (
<div
style={{ textDecoration: complete && 'line-through' }}
onClick={this.onClick}
>
{value}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: '1', value: 'to do this', complete: false },
{ id: '2', value: 'todo that', complete: true },
{ id: '3', value: 'will do it later', complete: false }]
}
}
toggleTodo = (todoId) => {
const { todos } = this.state;
const nextState = todos.map(todo => {
if (todo.id !== todoId) return todo;
return {
...todo,
complete: !todo.complete
}
});
this.setState({ todos: nextState });
}
render() {
const { todos } = this.state;
return (
<div >
{
todos.map((todo) => {
return (
<Todo
complete={todo.complete}
key={todo.id}
todoId={todo.id}
value={todo.value}
onClick={this.toggleTodo}
/>
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>