简体   繁体   中英

Creating and displaying new elements on event (React.JS)

I've used stack overflow for the last two years since I picked up programming, but I've never actaully asked a question directly. However, recently I've been running into a problem with React which has stumped me for the last week, and it feels like one of those problems a pro will look at and instantly know the answer to.

My code: https://codepen.io/gooeyWolf/pen/xxZGxGq

 <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> <body> <div id="root"><div id="shit"></div></div> <script src="https://unpkg.com/react@16.12.0/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone@7.8.3/babel.js"></script> <script type="text/babel"> class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: '',}; this.handleChange = this.handleChange.bind(this); } NextThing(){ const element = <h1>Hello, world;</h1>. const divy = document.body;getElementById("div2"). divy;appendChild(element). //document.body;appendChild(element). } handleChange(event) { this:setState({value. event.target;value}). } handleEnter(event){ if(event.key === 'Enter'){ this;NextThing(), } } render() { return ( <div> <span>Are you writing for a protagonist, antagonist, love interest? or secondary character. <br /></span> <input name="flip" id="lucky" value={this.state.value} onChange={this.handleChange} onKeyDown={this;handleEnter} /> <div id="div2"></div> </div> ). } } //onChange={this.handleChange} ReactDOM,render( <NameForm />. document;getElementById('root') ); </script> </body>

The app Im building is pretty simple. Basically, the app asks you a question, you type in your answer, and it displays a new block of text and a new input box to type your next answer, while leaving previous text on the screen. So far, when you type something and press enter, the programs handles the enter, and calls another function to display the next block of text. At first, i tried using return/render statements, but this cause the page to refresh, which would be fine, but the new elements I wanted to display doesn't show up. The React.JS course I'm taking advises against using creatElement statements for every new element required, so I'm working with appendChild , which for some reason this runs into errors. I feel like there has to be a more simple solution to this, and a more reusable solution too. Ideally, it would be able to handle a refresh and keep the previously added elements. I wrote this program in python first, with print and input statements, and it was so easy, but I spent the last week trying figure out the right way with React, but all my solutions seem unnecessarily complicated. There has to be a better way... Thank you guys so much, this helps more than I can express. Stack Overflow the best (:

In React, the common DOM manipulation functions ( appendChild , innerHtml etc) are not the "best" way to do this, there is a more usable solution as you said. What you can do, is take advantage of the way JavaSript expressions can be renderered inside the HTML (That is, those that are inside with {{variable}} with curly braces) and combine it with this.state .

First in this.state , define an array div2Children which will contain all the text that is typed into the input

// This is inside the constructor
this.state = {value: '', div2Children: []};

Then you can map through each element and convert them in HTML.

render() {
    return (
        <div>
            <span>Are you writing for a protagonist, antagonist, love interest, or secondary character? <br /></span>
            <input name="flip" id="lucky" value={this.state.value} onChange={this.handleChange} onKeyDown={this.handleEnter} />
            <div id="div2">
                {/* when the array changes this page will also change the HTML */}
                {this.state.div2Children.map(child => (
                    <h1>{child}</h1>
                ))}
            </div>
        </div>
    );
}

Then finally in your NextThing method you'll have to change with this.setState the div2Children array to have all the previous elements but also your newly typed (appending the last element but creating a new array)

NextThing(){
    this.setState({div2Children : [...this.state.div2Children, this.state.value]});
}

Note that you will also have to bind NextThing and handleChange . Here is a working version

 <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> <body> <div id="root"><div id="shit"></div></div> <script src="https://unpkg.com/react@16.12.0/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.12.0/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone@7.8.3/babel.js"></script> <script type="text/babel"> class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: '', div2Children: []}; // Bind all the functions this.NextThing = this.NextThing.bind(this); this.handleChange = this.handleChange.bind(this); this.handleEnter = this.handleEnter.bind(this); } NextThing(){ this.setState({div2Children: [...this.state.div2Children, this.state.value]}); } handleChange(event) { this.setState({value: event.target.value}); } handleEnter(event){ if(event.key === 'Enter'){ this.NextThing(); } } render() { return ( <div> <span>Are you writing for a protagonist, antagonist, love interest, or secondary character? <br /></span> <input name="flip" id="lucky" value={this.state.value} onChange={this.handleChange} onKeyDown={this.handleEnter} /> <div id="div2"> {this.state.div2Children.map((child, index) => ( <h1 key={index}>{child}</h1> ))} </div> </div> ); } } //onChange={this.handleChange} ReactDOM.render( <NameForm />, document.getElementById('root') ); </script> </body>

Also notice here that I am also setting a key attribute in the snippet above. You can find more about that here , I just provided a simple solution which should be fine.

Firstly, you can't access to the DOM elements like that in React. Check the refs react API. Anyway, I think you should reverse your reflection here and avoid those dom-manipulation (if you can).

The data should be enough to display questions and answers.

 class NameForm extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
       step: 1,
       values: [''],
       questions: [
         {
           id: 1,
           label: 'Are you writing for a protagonist, antagonist, love interest, or secondary character?',
         },
         {
           id: 2,
           label: 'Are you happy?',
         }
         // other questions
       ]};
   }
   NextThing = (next) => {
     this.setState((prev) => ({step: prev.step+1, values: [...prev.values, '']}));
   }
   handleChange = (index, event) => {
     event.persist()
     this.setState(state => {
       const values = state.values.map((item, j) => {
         if (j === index) {
           return event.target.value;
         } else {
           return item;
         }
       });

       return {
         values,
       };
     });
   }
   handleEnter = (index, event) => {
     if(event.key === 'Enter' && this.state.step < this.state.questions.length){
       this.NextThing(index);
     }
   }

   render() {
     const { questions, step, values } = this.state;

     return (
       <div>
         {questions.slice(0, step).map((question, index) => (
           <div key={question.id}>
             <span>{question.label}<br /></span>
             <input 
               name={"answer"+question.id} 
               id={"answer"+question.id}
               value={values[index]} 
               onChange={e => this.handleChange(index, e)} 
               onKeyDown={e => this.handleEnter(index, e)} />
           </div>
         ))}
       </div>
     );
   }
 }

ReactDOM.render(
  <NameForm />,
  document.getElementById('root')
);

Let me know if I misunderstood something or if it helps:D

As others wrote, you shouldn't try to manipulate DOM like regular javascript in React.

So; we shouldnt manipulate elements like regular js, but what we can do is we can change stuff inside HTML with Refs and States. You can use ref's to give an element some kind of "id" in React way, and change its properties. State is one of the key elements of React and its quite powerful.

https://reactjs.org/docs/state-and-lifecycle.html

We can toggle on and off divs, inputs and other elements visibility, innerTexts and etc.

https://reactjs.org/docs/hooks-state.html

I will use a State Hook to change displaying of a div in here:

const [testState, setTestState] = useState(false)

...

return (
<>
    <h1>Example Page</h1>
    {testState ? <input placeholder="Example Input" /> : null}
</>
)

If my testState is true; an input box will show up; and if its false nothing will. We can use setTestState(true) -or false- now somewhere in our code to change visibility rendered html block.

Now, not sure if i understood your problem and phyton code correctly (i probably didnt), i quickly made a demo with using state's to toggle an elements visibility.

https://codesandbox.io/s/zealous-taussig-0ctif

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