sthig
sthig

Reputation: 469

React.js "hide element and reveal next element" after submit

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

Answers (1)

btzr
btzr

Reputation: 2154

Questions list

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]

Question component

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

Next question

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

Completed

When all questions are answered, the Madlibs component is called.

{ this.state.completed ? <Madlibs /> : RenderQuestion }

Conditionals

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

Example

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

Related Questions