1

i am trying to render the Need component when we click on the "add" link. below is my code for the main component:

import React from 'react';
import ReactDOM from 'react-dom';
import { Hand } from './hand.js';
import { Need } from './need.js';

class App extends React.Component{
  constructor() {
    super();
    this.processHand = this.processHand.bind(this);
    this.addNeed = this.addNeed.bind(this);
    this.state = {
      inhandMoney : " ",
      renderNeed: false,
    }

  }

  processHand(e){
    e.preventDefault();
    const handMoneyReceived = this.handMoney.value;
    this.setState({
        inhandMoney: handMoneyReceived
    });     
  }

  addNeed(e){
    e.preventDefault();
    this.setState({
        renderNeed:true
    });
  }

  render(){ 

    const passNeed = (    
            <Need/>   
      );

    return(
        <div>
          <div className ="hand">
            <form onSubmit = {this.processHand}>
              <input type="text" ref= {ref => this.handMoney = ref}/>
              <input type="submit"/>
            </form>
            <Hand handMoney = {this.state.inhandMoney}/>
            <Need/>
          </div>
          {this.state.renderNeed ? passNeed : null}
          <a href="#" className="add" onClick = {this.addNeed}>add</a>
        </div>
      )
  }
}

ReactDOM.render(<App />, document.getElementById('container'));

and below is my Need component just in case:

import React from 'react';

export class Need extends React.Component{
constructor() {
    super();
    this.processNeed = this.processNeed.bind(this);
    this.state ={
        why: " ",
        howMuch: 0
    }

}

processNeed(e){
    e.preventDefault();
    const why=this.why.value;
    const howMuch=this.howMuch.value;
    this.setState({
        why:why,
        howMuch:howMuch
    });
}

    render(){
        return(
          <div className ="need">
            <form onSubmit = {this.processNeed}>
              <input type="text" ref= {ref => this.why = ref}/>
              <input type="text" ref= {ref => this.howMuch = ref}/>
              <input type="submit"/>
            </form>
            <div>
                <h1>{this.state.why}</h1>
                <h1>{this.state.howMuch}</h1>
            </div>
          </div>            
        )
    }
}

i am achieving what i am trying to achieve on the first click to the add link, i.e. at first the need component gets rendered without any condition.and when i click on "add" the Need component is rendered again but when i click the "add" link for the second time, i don't see any changes. why is that so, i want to render the Need component every time i click on "add" link.

Ashish
  • 409
  • 1
  • 9
  • 24
  • Class methods need to be "bound". @john_omalley has the answer below. See http://stackoverflow.com/a/30721098/368697 for binding class methods that you intend to use as callbacks. – Ross Allen Feb 13 '17 at 18:45
  • did you get the solution or still facing issue ?? – Mayank Shukla Feb 17 '17 at 06:06

7 Answers7

3

Below is the solution fiddle for your problem. I tried to be as close with your code as possible.

https://jsfiddle.net/birjubaba/t0yusos9/3/

The explanation given by @sean is spot on.

By default, calling setState will rerender component, no matter what valid arguments you pass to setState. So yes, it should be rerendering. It doesn't appear to be rerendering because, well, it's always displaying the same thing after the first click because renderNeed is always true after every click

React maintain a virtual DOM and all the DOM manipulations are done in this virtual DOM (before updating actual DOM). Adding Need component is a DOM manipulation. So, first time when we click on "add" it actually updates the virtual DOM to add a new Need. Now react diff algorithm works and it updates the actual DOM. So, Need component is displayed in UI. The DOM will look something like below:

div 
 |- div (class=hand)
      |-form
        |-input (type=text) 
        |-input (type=submit)
      |- Hand
      |- Need
 |-Need (added by clicking on add anchor tag)
 |-a (class=add)

Next time when "add" is clicked, this.state.renderNeed is true again, so, React will try add Need component in virtual DOM (due to below code).

{this.state.renderNeed ? passNeed : null}
<a href="#" className="add" onClick = {this.addNeed}>add</a>

But before adding Need, React diff algorithm will run and it will see there is already a Need component present in Virtual (and actual) DOM which is exactly similar to the one it was trying to add. React will think both the DOM are same and nothing is updated in UI.

Your requirement of add is something like below:

div 
 |- div (class=hand)
      |-form
        |-input (type=text) 
        |-input (type=submit)
      |- Hand
      |- Need
 |-Need (added by clicking on add anchor tag)
 |-Need (added by clicking on add anchor tag)
 |-Need (added by clicking on add anchor tag)
 |-Need (added by clicking on add anchor tag)
 |-a (class=add)

This can be achieved by maintaining an array of Need component and rendering them on clicking add. React diff algorithm will take care of all the optimization for updating virtual DOM and actual DOM.

I would encourage you to go through below two wonderful sites, where the react diff algorithm is properly explained.

  1. Search "Reconciliation - React" in Google and open the facebook react page from search results
  2. https://calendar.perfplanet.com/2013/diff/
  • Basic rule of `&&` operator, If first one is true then second condition is evaluated. So here if `this.state.renderNeed` is true then second condition which is a method call (this.renderNeedArray()` is evaluated. It just makes sure that when `this.state.renderNeed` is true then only `renderNeedArray()` is called. – Hardik Modha Feb 17 '17 at 18:14
  • thanks, also i would like to add all the values of "how Much" in the all need component after i have completed adding need. is there a way to do so? – Ashish Feb 17 '17 at 18:15
  • sorry, i deleted the previous question before you have answered. thanks anyways. – Ashish Feb 17 '17 at 18:16
1

Try: <a href="#" className="add" onClick = {this.addNeed.bind(this)}>add</a>

john_omalley
  • 1,398
  • 6
  • 13
  • Right - missed that. You would need to model an array of items in the state - instead of starting with a boolean flag of false start with [] and then concat a new item with every click. you then need to render one item for each item in the array. see react docs on rendering arrays of items (esp. the 'key' property. – john_omalley Feb 13 '17 at 19:08
1

If you want to keep rendering a collection of Needs, you may want to store an array in your state that can keep track of your needs.

Otherwise, based on this quote -

at first the need component gets rendered without any condition.and when i click on "add" the Need component is rendered again but when i click the "add" link for the second time, i don't see any changes.

It seems you want to render based on changes tracked by your renderNeed boolean

By default, calling setState will rerender component, no matter what valid arguments you pass to setState. So yes, it should be rerendering. It doesn't appear to be rerendering because, well, it's always displaying the same thing after the first click because renderNeed is always true after every click

this is a good resource: ReactJS - Does render get called any time "setState" is called?

As a contrived example, if you really want to "rerender" your component, you could do something trivial like creating a toggle for renderNeed

  addNeed(e){
    e.preventDefault();
    this.setState((prevState, props) => ({
        renderNeed: !prevState.renderNeed
    });
  }

It's not recommended you do this because your addNeed function is not doing what it name suggests...

also a little fiddle to demonstrate that it is re-rendering

https://jsfiddle.net/jwm6k66c/2131/

Hope this helps.

Community
  • 1
  • 1
Sean Kwon
  • 907
  • 6
  • 12
1

From your explanation, I get that you want to add Need component every time the add link is clicked.

The code you provided doesn't work the way you want it to work because of your wrong understanding about render() method. Everytime you click on add link, your <Need /> component will re-render but there is only one <Need /> component which re-renders every time. If you want to render new component every time add link is clicked. You will need to use an array which will contain all <Need /> components.

To achieve the same, I've created a state variable named needArray. Everytime you click on add link, a new <Need /> component will be added in this state variable and the same will be rendered.

constructor(props) {
    super(props)
    this.state = {renderNeed: false, needArray: []}
    this.addNeed = this.addNeed.bind(this)
}

addNeed(e) {
    e.preventDefault();
    // any another better approach can be used to generate unique key. I've used Math.random() for demo only.
    this.setState({
        renderNeed: true, 
        needArray: [<Need key={Math.random()}/>, ...this.state.needArray]
     })
}

render() {
    return (
      <div>
        <a href="#" onClick = {this.addNeed}>add</a>
        {this.state.renderNeed ? this.state.needArray : null}
      </div>
    )
}

Here is the link to the working fiddle. For brevity, I've added only code that is needed. JSFiddle

Hardik Modha
  • 12,098
  • 3
  • 36
  • 40
0
  {this.state.renderNeed ? passNeed : null}

replace with

 {this.state.renderNeed && <Need/> }
Vladu Ionut
  • 8,075
  • 1
  • 19
  • 30
0

1) One way to achieve this is:

addNeed(e){
  e.preventDefault();
  this.setState({
      needKey: Math.random()
  });
}

And in your render you can add this key:

<Need key={this.state.needKey} /> 

2) Another way is:

addNeed(e){
  e.preventDefault();
  this.setState(this.state);
}

3) Also, can be done with forceUpdate. However, it is not generally recommended:

addNeed(e) {
  e.preventDefault();
  this.forceUpdate()
}
Shota
  • 6,910
  • 9
  • 37
  • 67
0

I would create an initial state key with an empty array, every time you click 'add' you would increment the size of the array, and then you would map the array at the render(). Something like this:

constructor() {
super();
  this.processHand = this.processHand.bind(this);
  this.addNeed = this.addNeed.bind(this);
   this.state = {
    inhandMoney : " ",
    fields: [],
   }
}

addNeed(e){
  e.preventDefault();
  this.setState({
    fileds: this.state.fields.push(1),
  });
}

render() {
  <div>
    <div className="hand">
      <form onSubmit={this.processHand}>
        <input type="text" ref={ref => this.handMoney = ref}/>
        <input type="submit"/>
      </form>
      <Need/>
    </div>
    {this.state.fields.map(i => <Need key={i} />)}
    <a href="#" className="add" onClick={this.addNeed}>add</a>
  </div>
}