简体   繁体   中英

React.js focus on text input after submit/enter not working

I have a component that when the user hits submit (or enter) a new question is presented with a text input, the users enters in an answer and hits submits (this repeats until the final question is answered).

All works except that it's not focusing no matter if I add a ref or autofocus. I'm unsure what to do at this juncture and can't seem to get it to work no matter what I try.

Could I get some assistance on, when the user hits submit, it focuses on the next text input. I'm using styled components below in my code so the text input would be inputStyle

 class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.focus = this.focus.bind(this);
    this.state = {
      value1: '',
      value2: '',
      value3: '',
      value4: '',
      value5: '',
      value6: '',
      value7: '',
      newValue: '',
      submitted: false,
      input1: 0,
      input2: 0,
      input3: 0,
      input4: 0,
      input5: 0,
      input6: 0,
      input7: 0,
      display: 'block',
      currentStep: 1,
      whitebox: 'block'
    };

    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();
    if (this.state.currentStep > 6) {
      this.setState({ visible: !this.state.visible });
      this.setState({ display: 'none' });
      this.setState({ whitebox: 'none' });
    } else {
      this.setState({ currentStep: this.state.currentStep + 1 });
    }
  }

  inputHolderStyle(style, step) {
    const displayProp = step === this.state.currentStep ? 'block' : 'none';

    return {
      // background: `url(${style}) no-repeat center center`,
      // backgroundSize: 'cover',
      // border: 'white 1px solid',
      // background: '#00B5DE',
      display: displayProp
    };
  }

  focus() {
    // Explicitly focus the text input using the raw DOM API
    this.textInput.focus();
  }

  render() {
    const divStyle = {
      marginTop: '50px',
      color: 'white',
      top: '25px',
      position: 'absolute',
      width: '320px',
      textAlign: 'center',
      border: 'white 1px solid',
      padding: '1em',
      borderRadius: '3px',
      display: this.state.whitebox
    };
    let question = null;
    const show = this.state.visible;
    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 (
      <MainContainer>
        <div style={divStyle}>
          <form
            style={{ display: this.state.display }}
            onSubmit={this.handleSubmit}
          >
            <InputHolder style={this.inputHolderStyle(ml1, 1)}>
              <InputQuestion>1. Enter A Proper Noun</InputQuestion>
              <label>
                <InputStyle
                  name="input1"
                  type="text"
                  value={this.state.value1}
                  placeholder="Proper Noun"
                  onChange={this.handleFirstChange}
                  ref1={input => {
                    this.textInput = input;
                  }}
                />
                <GrammarNerd>
                  Hint: Use words like Rebel, Hell's Angels, Vegan
                </GrammarNerd>
              </label>
            </InputHolder>
            <InputHolder style={this.inputHolderStyle(ml2, 2)}>
              <InputQuestion>2. Enter A Location</InputQuestion>
              <label>
                <InputStyle
                  name="input2"
                  type="text"
                  ref={input => {
                    this.textInput = input;
                  }}
                  value={this.state.value2}
                  placeholder="Noun"
                  onChange={this.handleSecondChange}
                  ref2={input => {
                    this.textInput = input;
                  }}
                />
                <GrammarNerd>
                  Hint: Use a word such as Base, Bunker, Foxhole, Bedroom
                </GrammarNerd>
              </label>
            </InputHolder>
            <InputHolder style={this.inputHolderStyle(ml3, 3)}>
              <InputQuestion>
                Enter A Proper Noun that Describes Evil
              </InputQuestion>
              <label>
                <InputStyle
                  name="input3"
                  type="text"
                  placeholder="Enter a Proper Noun"
                  value={this.state.value3}
                  onChange={this.handleThirdChange}
                  ref3={input => {
                    this.textInput = input;
                  }}
                />
              </label>
              <GrammarNerd>
                Hint: Use words like Empire, Ottoman, Mongols
              </GrammarNerd>
            </InputHolder>
            <InputHolder style={this.inputHolderStyle(ml3, 4)}>
              <InputQuestion>Describe Something Menacing</InputQuestion>
              <label>
                <InputStyle
                  name="input4"
                  type="text"
                  placeholder="Enter a Proper Name"
                  value={this.state.value4}
                  onChange={this.handleFourthChange}
                  ref4="theDiv"
                />
                <GrammarNerd>
                  Hint: Freeze Ray, Mother of All Bombs, Leftover Fruitcake
                </GrammarNerd>
              </label>
            </InputHolder>
            <InputHolder style={this.inputHolderStyle(ml3, 5)}>
              <InputQuestion>Describe a fortified area</InputQuestion>
              <label>
                <InputStyle
                  name="input5"
                  type="text"
                  placeholder="Enter a Noun"
                  value={this.state.value5}
                  onChange={this.handleFifthChange}
                  ref5={input => {
                    this.textInput = input;
                  }}
                />
                <GrammarNerd>
                  Hint: Castle, Bunker, Planet, Safe Space
                </GrammarNerd>
              </label>
            </InputHolder>
            <InputHolder style={this.inputHolderStyle(ml3, 6)}>
              <InputQuestion>A Woman's Name</InputQuestion>
              <label>
                <InputStyle
                  name="input6"
                  type="text"
                  placeholder="A Woman's Name"
                  value={this.state.value6}
                  onChange={this.handleSixthChange}
                  ref6={input => {
                    this.textInput = input;
                  }}
                />
              </label>
              <GrammarNerd>
                Hint: Astrid, Diana, Mononoke, Peach{' '}
              </GrammarNerd>
            </InputHolder>
            <InputHolder style={this.inputHolderStyle(ml3, 7)}>
              <InputQuestion>Describe a large area of mass</InputQuestion>
              <label>
                <InputStyle
                  name="input7"
                  type="text"
                  placeholder="Enter a Noun"
                  value={this.state.value7}
                  onChange={this.handleSeventhChange}
                  ref7={input => {
                    this.textInput = input;
                  }}
                />
              </label>
              <GrammarNerd>
                Galaxy, Planet, Wal Mart
              </GrammarNerd>
            </InputHolder>
            <InputHolderSubmit>
              <SubmitButton onClick={this.focus} type="submit" value="Submit" />
            </InputHolderSubmit>
          </form>
        </div>
        <NextQuestion>
          {question}
        </NextQuestion>
      </MainContainer>
    );
  }
}

export default NameForm;

I have left some of my code I've tested (ref and autofocus) as well as some of the functions that don't seem to break the code, but are not working either.

Thank you for your help

WORKING EXAMPLE AUTOFOCUS DEMO

The following code is a simplified sample implementation of what are you looking to do.... check it out and run it and let me know if this helps!!! Happy coding =]!

import React, { Component } from "react";

export default class AutoFocusText extends Component {
    constructor() {
        super();

        this.state = {
            active: 0,
            questions: [
                "how are you?",
                "whats your name?",
                "is reactjs awesome?"
            ],
            value: "",
            answers: []
        };

        this.submitHandler = this.submitHandler.bind(this);
        this.renderQuestion = this.renderQuestion.bind(this);
        this.onChange = this.onChange.bind(this);
    }

    renderQuestion() {
        const { questions, active, value } = this.state;

        if (active >= questions.length) return <div>You're Done!</div>;

        return questions
            .filter((quest, index) => index === active) // get next question
            .map(quest =>    // map over selected question, the key prop allows react to
                <FormElement // unmount and mount the components properly, thereby focussing correctly
                    key={active}
                    text={quest}
                    value={value}
                    onChange={this.onChange}
                />
            );
    }

    onChange(e) {
        this.setState({ value: e.target.value });
    }

    submitHandler(e) {
        e.preventDefault();

        const answers = [...this.state.answers, this.state.value]; //push new value to answsers array without mutation
        const value = ""; // clear input
        const active = this.state.active + 1; // index pointer

        this.setState({ answers, value, active });
    }

    render() {
        return (
            <div>
                {/* Form Wrapper */}
                <form onSubmit={this.submitHandler}>
                    {this.renderQuestion()}
                    <button type="submit">Submit</button>
                </form>
                <ul>
                    {this.state.answers.map((ans, index) => {
                        return (
                            <li key={index}>
                                {ans}
                            </li>
                        );
                    })}
                </ul>
            </div>
        );
    }
}

Here is the FormElement component that manages focusing the text input...

class FormElement extends Component {
    constructor() {
        super();
    }

    componentDidMount() {
        //focus text input upon mounting component 
        this.textInput.focus();
    }

    render() {
        const { text, value, onChange } = this.props;

        return (
            <div>
                <p>
                    {text}
                </p>
                <input
                    ref={el => {
                        this.textInput = el;
                    }}
                    onChange={onChange}
                    type="text"
                    value={value}
                />
            </div>
        );
    }
}

For fixing consider the following

  1. Since you are hiding each question using display:none, they are still rendered into the DOM, thus every ref will be executed regardless of which question you are on. You are assigning to this.textInput so this.textInput will only ever have input7's element.
  2. For the same reason, autofocus probably won't work very well as I believe this fires when a react component is added to the DOM, not made visible with css.

If I were you I would have a renderQuestion( step ) method with a switch statement in, which returns the JSX for each case of step . Call this after the form element in your main render method with {this.renderQuestion(step)}, do the switch and this way only the active question will be rendered into the DOM. This may help making autoFocus work, I'm not sure. But to be more explicit, ref will now work properly and you can simply do

componentDidUpdate() {
  this.textInput && this.textInput.focus();
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM