I am very new to React ,trying to develop a Quiz with questions,I have Quiz component which recieve array which is Json with the questions. This is the Quiz class :
class Quiz extends Component {
constructor(props){
super(props);
this.state={
questions: this.props.jsonArray,
score: 0,
currentQuest: 0,
guessedAnswer:'',
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange= this.handleChange.bind(this);
}
handleSubmit(event){
console.log("In parent submited");
let correct_answer= this.state.questions[this.state.currentQuest].correct_answer;
console.log("Correct answer is " + correct_answer);
if(this.state.guessedAnswer===correct_answer){
console.log('Correct answer');
this.setState({score : this.state.score + 1});
}
console.log('Your score is ' + this.state.score);
this.setState({currentQuest : this.state.currentQuest + 1});
console.log("Current question is " + this.state.currentQuest);
}
handleChange(val){
this.setState({guessedAnswer: val});
}
render(){
return(
<div>
{this.state.currentQuest< this.state.questions.length ? <QuestComp question={this.state.questions[this.state.currentQuest]} onChangeValue={this.handleChange} onFormSubmit={this.handleSubmit}/> :null}
</div>
);
}
}
It calls the Question component with parameter ,the current question ,here is the Question component :
class QuestComp extends Component{
constructor(props){
super(props);
this.state={
question: this.props.question,
rerender: false
}
console.log('In constructor in question');
}
updateAnswer=(e)=>{
console.log('changed ' + e.target.value);
this.props.onChangeValue(e.target.value);
}
submitForm=(e)=>{
e.preventDefault();
this.setState({question : this.props.question});
this.props.onFormSubmit(e);
}
render(){
console.log("Rerender child" + this.props.question.question);
let incorrect_answers=[];
for(let i=0;i<this.state.question.incorrect_answers.length;i++){
incorrect_answers.push(this.state.question.incorrect_answers[i]);
}
let randNum=Math.round(Math.random()* (incorrect_answers.length-1));
let correct_answer=this.state.question.correct_answer;
incorrect_answers.splice(randNum,0,correct_answer);
return(
<form onSubmit={this.submitForm}>
<h2><p>{this.state.question.category}:</p></h2>
<h3><p>The question is {this.state.question.question}</p></h3>
{incorrect_answers.map((answer,i) => <div key={i}><input name="somename" onChange={this.updateAnswer} type="radio" key={i} value={answer} />{answer} </div>)}
<input type="submit" className="btn btn-success" value="Submit"/>
</form>
);
}
}
The idea is simple everytime a user submit the form I increment the currentQuestion state and pass the next question to QuestComp to display it ,the problem is the first time I have to click the submit button actually 2 times to go to the next question ,it just not render it and when i put console.log in the QuestComp render method to see what question it recieved ,actually it is the right one ,it just dont display it I dont know why ,so the first time I have to press 2 times the submit button to go to the next question ,after that it is working fine ,one press and renders the next question ,any idea why ?
The main problem is related to the question state in QuestComp is out of sync with the question props passed from Quiz.
Just use the props passed from Quiz directly instead of setting the props to the state in QuizComp. Setting props to state like this is an anti-pattern and error prone.
So to solve this, just replace all your this.state.question in QuestComp with this.props.question.
setState is asynchronous and it takes some time to complete. Your this.props.onFormSubmit(e); triggers right after and doesn't wait for it to end.
Try using it this way:
this.setState({question : this.props.question},
() => this.props.onFormSubmit(e);
);
It should trigger after setState end and make your stuff work.
When you click submit for the first time you run this.setState({ question: this.props.question }); with this.state.questions[this.state.currentQuest].
The current value of currentQuest is 0 so it shows your question with index = 0 again. handleSubmit increases it to 1 right after so every next go is good.
You need to increase that number before.
Related
Im new to reactjs. Im trying to create a comment section for some uploaded files, and keeping a counter on the comment buttons attached to each file. However, the counter is returning strange values.
Here is the relevent code:
class ListItem extends React.Component {
constructor(props){
super(props)
this.clicked = false
this.commentButtonRef = React.createRef();
this.state = {clickCounter:0, counterMat:[]}
}
handleClick = () =>{
console.log(this.state.clickCounter)
this.clicked = true;
this.counterMat = []
this.props.onCommentButtonClick(this.props.file, this.clicked)
this.clicked = false;
//update click counter
this.setState({clickCounter:this.state.clickCounter + 1}, this.updateCounterMatrix())
}
updateCounterMatrix = ()=> {
const temp = this.state.counterMat.slice() //copy the array
temp[1] = this.state.clickCounter //execute the manipulations
this.setState({counterMat: temp},console.log(this.state.counterMat, this.state.clickCounter))
}
createCounterMat=(element)=>{
// use ref callback to pass DOM element into setState
this.setState({counterMat:[element,this.state.clickCounter]})
console.log(this.counterMat)
}
render(){
return(
<div className="item">
<i className="large file alternate icon"></i>
<div className="content">
<div className="header">{this.props.file}</div>
<button className='comment-button'
id = {this.props.file}
onClick = {this.handleClick}
key = {this.props.file}
ref = {this.createCounterMat}
clickcounter = {this.state.clickCounter}
> Comment</button>
</div>
</div>
)
}
}
Here are the issues im having:
1) As soon as this page first renders, my use of a reactRef callback function createCounterMat in the button element should console log's undefined, which is unexpected.
2) On the first click of my button, the handleClick function calls correctly. However, the console log's inside both handleClick and updateCounterMatrix both return a value of 0 for this.state.clickCounter. I expected the first to be 0, but the second console.log to be 1 by this stage.
3) On the second click, the clickCounter state seems to correctly increment by 1. However, the console.log(this.state.counterMat, this.state.clickCounter) gives a value of 0 inside this.state.counterMat, and a value of 1 in the case of simply this.state.clickCounter.
Here is a screenshot showing all of this
Can anyone help me work out what's going on?
You're calling console.log before the set state, not after. This:
this.setState(
{counterMat: temp},
console.log(this.state.counterMat, this.state.clickCounter)
)
... means "call console.log, then pass its result along with {counterMat: temp} into this.setState". You probably meant to do:
this.setState(
{counterMat: temp},
() => console.log(this.state.counterMat, this.state.clickCounter)
)
I am working on a React application where I am trying to render text on the screen when a button is clicked. I have defined a function onButtonClick which gets triggered whenever the button is clicked. However, the HTML that I am returning from the function is not rendered on the screen. I am in the learning stages of React so please excuse me if the question seems silly.
class App extends Component {
constructor() {
super();
this.state = {
blockno:0
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1})
return(
<div>
<h3>Some text</h3>
</div>
);
}
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
</div>
);
}
}
The value is being returned, but the framework/browser/etc. has no reason to do anything with that value.
Try thinking about this a different way, a "more React way". You don't want to return the value to be rendered, you want to update state. Something like this:
constructor() {
super();
this.state = {
blockno:0,
showDiv: false // <-- note the new property in state
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1, showDiv: true})
}
Now you're not returning anything, but rather updating the state of the component. Then in your render method you conditionally render the UI based on the current state:
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
{
this.state.showDiv
?
<div>
<h3>Some text</h3>
</div>
: ''
}
</div>
);
}
The click handler doesn't modify the page, it just modifies the state of the component you're writing. The render method is responsible for rendering the UI based on that state. Any time state changes, render will be called again to re-render the output.
(Note: It's not 100% clear if this is exactly the functionality you're looking for in the UI, since it's not really clear what you're trying to build. But the point here is to illustrate how to update state and render output in React. Your logic can be tweaked as needed from there.)
You have to make a render based on your state. Please check the tutorial at the react docs to learn more about how React works. It's really good
Here is a version of your code that works. Hope it helps
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
};
}
OnButtonClick = () => {
//updates the states
this.setState({ blockno: this.state.blockno + 1 });
};
//remember: every time there is an update to the state the render functions re-runs
render() {
//variable holding the blocks in an array
let blocks = []
//if blockno is greater than 0, it checks everytime that there is a state change
if (this.state.blockno > 0) {
//for every block added
for (let index = 0; index < this.state.blockno; index++) {
//We`re going to add to the array of blocks a new div with the block number
blocks.push(
<div>
<h3>My block number is {index}</h3>
</div>
);
}
}
return (
<div>
<div>
{/**button that updates the state on every click */}
<button onClick={this.OnButtonClick}>
Click me to add a new div!
</button>
</div>
{/**This render the blocks variable that holds the divs */}
{blocks}
</div>
);
}
}
What I see is that you are trying to build a counter. The value that you're returning from the click handler function can't be rendered, instead you need to manage it in the render function as follow:
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
}
}
OnButtonClick = () => {
this.setState(prevState => ({ blockno: prevState.blockno + 1 }));
}
render() {
return(
<div>
{this.state.blockno > 0 && <div>some text {this.state.blockno}</div>}
<Button onButtonClick={this.OnButtonClick} />
</div>
);
}
}
Also note that the setState method is asynchronous, please read the documentation https://reactjs.org/docs/react-component.html#setstate
I'm already develop a timer on which we can enter a name and time in seconds to start the timer.
I want now to create a button and if I click, it display another timer with name, and time in seconds.
I don't know how I can do this...
Here is the timer with the inputs... https://jsfiddle.net/q806zeps/37/
I think if I can duplicate this ReactDOM.render(<App/>, document.getElementById('timer')); but I thonk it's not possible ?
Thanks
Please have a look at this code, hope it solves your problem.
class App extends React.Component {
constructor(props){
super(props)
this.state = {
counter : 1
}
}
//add timer code by oneshubh
addTimer(){
this.setState({counter: this.state.counter+1})
}
render(){
var timerDom = [];
for(var i = 0;i<this.state.counter;i++){
timerDom.push(<Wrapper />)
}
return (
<div>
{timerDom}
<button onClick={()=>this.addTimer()} >add timer</button>
</div>);
}
}
You can use an array on the state of Wrapper to track the individual timers within your application.
state: {
timers: []
}
Clicking the button, adds an object into your state with your current keys.
{
libelle: 'Input name',
seconds: 10
}
Iterate over this array to render you Minuteur components by passing the correct index.
Here is an updated fork of your fiddle. Creating timers using an array
I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen
I have a drop down component that looks like this:
{...}
this.state = {
isVisible: false
}
}
toggleDisplay() {
this.setState({isVisible: !this.state.isVisible});
}
render() {
return (
<div>
<button onClick={this.toggleDisplay()}>click</button>
{this.state.isVisible ? <MenuElements toggleDisplay={this.toggleDisplay} /> : '' }
</div>
)
}
}
"MenuElements" is just a ul that has a li. On another page i am using this component multiple times, so whenever i click on the button, "MenuElements" is shown for each click. The problem is that i want only one component to be displayed. So if a MenuElements component is already displayed, if i click on another button, it closes the previous component, and opens the second one.
How could this be implemented in my code?
Thanks.
You will somehow need to have a single state that defines which MenuItem is displayed. You could go with a global state with something like Redux, but if you are trying to build a reusable component, I guess it'd be best to wrap all of the MenuItem components in a parent component and keep a state there. That, I think, is the React way of doing it. Read this for an idea of how to design components: https://facebook.github.io/react/docs/thinking-in-react.html.
BTW, I think there is an error in the Button onClick handler. It should be:
<button onClick={this.toggleDisplay.bind(this)}> // or bind it somewhere else
Also, the correct way to change state based on previous state is this:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
I'd say this is du to the context of your callbacks. Have you tried forcing the context ?
<div>
<button onClick={this.toggleDisplay.bind(this)}>
click
</button>
{this.state.isVisible ?
<MenuElements toggleDisplay={this.toggleDisplay.bind(this)} />
: '' }
</div>