Reputation: 469
I'm working on a Mad Libs game where the user is presented a question such as:
Enter a Noun
The user fills out the proper name, hits submit and then that text input is now hidden and the next question is revealed.
I know it deals with conditional rendering to get this to work, but I'm unsure how and where to get started. I wrote out a user story to try to follow along when writing it out but still get a little lost. My user story is
When user enters in text and hits submit (or the enter key) then the text input is hidden (but not deleted or removed because the value entered is used to pass props) and the next text input with a new input is revealed until. When all questions are answered, the Madlibs component is called.
Everything is built out and working except the user story above. I am not getting an error at this time, I simply don't know how to write out a conditional function for this.
Here is my code:
import React, { Component } from 'react';
import styled from 'styled-components';
import Crawler from './crawler';
const NextQuestion = styled.div`
position: absolute;
color: white;
display: block;
margin-top: 108px;
`;
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value1: 'enter proper name',
value2: 'noun',
value3: 'enter another proper name',
newValue: '',
submitted: false,
input1: 0,
input2: 0,
input3: 0,
input4: 0,
input5: 0,
input6: 0,
input7: 0
};
this.handleFirstChange = event => this.handleChange(event, 'value1');
this.handleSecondChange = event => this.handleChange(event, 'value2');
this.handleThirdChange = event => this.handleChange(event, 'value3');
this.handleFourthChange = event => this.handleChange(event, 'value4');
this.handleFifthChange = event => this.handleChange(event, 'value5');
this.handleSixthChange = event => this.handleChange(event, 'value6');
this.handleSeventhChange = event => this.handleChange(event, 'value7');
this.handleSubmit = event => this._handleSubmit(event);
}
handleChange(event, type) {
let newState = {};
newState[type] = event.target.value;
this.setState(newState);
}
_handleSubmit(event) {
event.preventDefault();
let toggle = this.state.visable;
this.setState({ visable: !toggle });
}
render() {
const divStyle = {
marginTop: '50px',
color: 'white',
top: '25px',
position: 'absolute'
};
let question = null;
const show = this.state.visable;
if (show) {
question = (
<div>
<Crawler
properName1={this.state.value1}
noun1={this.state.value2}
properName2={this.state.value3}
properName3={this.state.value4}
noun2={this.state.value5}
personsName1={this.state.value6}
noun3={this.state.value7}
/>
</div>
);
}
return (
<div>
<div style={divStyle}>
<form onSubmit={this.handleSubmit}>
<label>
Proper Name:
<input
name="input1"
type="text"
value={this.state.value1}
onChange={this.handleFirstChange}
/>
</label>
<label>
Noun:
<input
name="input2"
type="text"
value={this.state.value2}
onChange={this.handleSecondChange}
/>
</label>
<label>
Another Proper Name:
<input
name="input3"
type="text"
value={this.state.value3}
onChange={this.handleThirdChange}
/>
</label>
<label>
And Another Proper Name:
<input
name="input4"
type="text"
value={this.state.value4}
onChange={this.handleFourthChange}
/>
</label>
<label>
Noun:
<input
name="input5"
type="text"
value={this.state.value5}
onChange={this.handleFifthChange}
/>
</label>
<label>
Person's Name:
<input
name="input6"
type="text"
value={this.state.value6}
onChange={this.handleSixthChange}
/>
</label>
<label>
Another Noun:
<input
name="input7"
type="text"
value={this.state.value7}
onChange={this.handleSeventhChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
<NextQuestion>
{question}
</NextQuestion>
</div>
);
}
}
export default NameForm;
I'm using this exercise to teach me more about React and conditions. Thank you for your help.
Upvotes: 0
Views: 2116
Reputation: 2154
First you need to create a list of questions: array
const QuestionsList = [
{
// Your question title
q: "Are you a human?",
// Validate the user answer
a: (answer) => (answer === 'yes'),
// Force user to give a correct answer
required: true
// Add more properties
...
},
...
];
You can navigate trough your questions setting the index
of the array:
this.state = { current: 0 }
...
// Select current question
QuestionList[this.state.current]
// Select next question
QuestionList[this.state.current + 1]
// Select last question
QuestionList[QuestionList.length - 1]
When user enters in text and hits submit then the text input is hidden (but not deleted or removed because the value entered is used to pass props) and the next text input with a new input is revealed until.
Since you are only using an input [text]
, you can use a single component for all your questions,
no need to handle conditionals
here:
const RenderQuestion = (
<div>
<h2>{question.q}</h2>
<input type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<button onClick={this.handleSubmit}>
Submit
</button>
</div>
);
React will update the component content trough state
,
this creates the effect of hide previous / show next
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state
To fix this use the second form of setState
:
this.setState(prev => {
// Get previous state
const {value, current} = prev;
//If input not empty
if (value.length > 0) {
// Validate answer: true / false
const validation = questions[current].a(value);
//If not last question
return (prev.current < index) ?
//Select next question / reset input
{
current: prev.current + 1,
value:''
} :
// else: Completed!
{ completed: true };
}
});
See: State Updates May Be Asynchronous
When all questions are answered, the Madlibs component is called.
{ this.state.completed ? <Madlibs /> : RenderQuestion }
If the condition is true, the element right after && will appear in the output. If it is false, React will ignore and skip it.
condition === true && element / method
Another method for conditionally rendering elements inline is to use the JavaScript conditional operator:
condition ? true : false
See: Inline If with Logical && Operator
const QuestionsList = [
{
q: "What's your name?",
a: (answer) => /^[A-Za-z\s]+$/.test(answer),
// Force user to give a correct answer
required: true
},
{
q: "Type a color:",
a: (answer) => {
const colors = ['blue', 'green', 'red', 'yellow', 'black', 'brown', 'orange', 'pink', 'purple', 'white', 'gray']
return colors.includes(answer);
},
required: true
},
];
class Questionary extends React.Component {
constructor(props){
super(props);
//Init state
this.state = {
current: 0,
value:'',
completed: false,
data: []
};
// Bind handlers
this.handleSubmit = this.handleSubmit.bind(this);
this. handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit () {
const {questions} = this.props;
const index = questions.length - 1 ;
let state = {};
this.setState(prev => {
// Vars
const { value, current } = prev;
const question = questions[current];
const validation = question.a(value);
//If input not empty
if (value.length > 0) {
// Debug validation
!validation && console.log("Please enter a valid value!");
//Force user to give the correct answer)
if (question.required && validation == false) return state;
// Data for the template string
let data = prev.data;
data.push(value);
//If not last question
state = (prev.current < index) ?
//Select next question and reset input
{
current: prev.current + 1,
value:'',
data
} :
// else: Completed!
{ completed: true, data };
return state;
}
// Debug input
console.log("Empty input!");
});
}
render() {
// Get all questions
const { questions } = this.props;
//Select question
const question = questions[this.state.current];
// Question component
const RenderQuestion = (
<div>
<h2>{question.q}</h2>
<input type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<button onClick={this.handleSubmit}>
Submit
</button>
</div>
);
// Score component
const RenderTemplates = (
<div>
<h2>Completed!</h2>
<p>
This is the story of
<span>{this.state.data[0]}</span>
and how the
<span>{this.state.data[1]}</span> dragon...
</p>
<p>
<span>{this.state.data[0]}</span> found a
<span>{this.state.data[1]}</span> goblin in the woods and then...
</p>
</div>
);
return (
<div>
{
this.state.completed ?
RenderTemplates : RenderQuestion
}
</div>
);
}
}
ReactDOM.render(<Questionary questions={QuestionsList}/>, document.getElementById("root"));
#root {
font-family: Arial, sans-serif;
color: #252525
}
h2, input[type=text] {
margin: 10px;
}
button {
border: 0;
border-radius: 3px;
background: #416dea;
padding: 5px 10px;
color: #FFF;
font-weight: bold;
border-radius: 3px;
}
p {
padding: 10px;
background: #ddd;
}
p span {
padding: 2px 4px;
margin: 0 10px;
background: #FFF;
color: #416dea;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div id="root"></div>
Upvotes: 1