React JS - getting a separate component to re-render

I have two components. One, called “GenerateRecipesFromList,” is a series of “boxes” containing the title of the recipes, and another is a child component, called “AddButon.” Right now the Add button can update localStorage, but why does it not “trigger” a change of state and a re-render in the parent component?

My CodePen for this is here

var GenerateRecipesFromList= React.createClass({
    getInitialState: function(){
      const defaultData = [["Spaghetti", "pasta, oil, sauce, parsely, cheese"], ["PB&J", "PB, J"]]
      const localData = JSON.parse(localStorage.getItem('reclist'));
      return {
        reclist: localData ? localData : defaultData
      }
  },  
  render: function(){
    var testData = JSON.parse(localStorage.getItem('reclist'));
   if(testData === null){
        localStorage.setItem('reclist', JSON.stringify(this.state.reclist));
        }
        var currentData = JSON.parse(localStorage.getItem('reclist'));
        var rows = [];
          for(var i=0; i<currentData.length; i++){
       var thedivname = i;
          rows.push(<div id= {this.thedivname} className="individual"> <span><h2>{this.state.reclist[i][0]}</h2></span> 
        </div>);
        }
      return(
        <div className="centerMe">
        <AddButton />
        {rows}</div>
      );
    },
});

var AddButton = React.createClass({

      overlayAdd: function() {
    var el = document.getElementById("overlay");
      el.style.visibility = (el.style.visibility === "visible") ? "hidden" : "visible";
      },

      exposeAddRecipe: function(){
          var exposeCurrentData = [];
          var userInput = [];
          exposeCurrentData = JSON.parse(localStorage.getItem('reclist'));
         var newTitle = document.getElementById("title").value;
         var newIngredients = document.getElementById("ingredients").value;
  
         userInput.push(newTitle);
         userInput.push(newIngredients);
         exposeCurrentData.push(userInput);
          localStorage.setItem('reclist', JSON.stringify(exposeCurrentData));
          this.setState({ reclist: exposeCurrentData});
         this.overlayAdd();
      },

      render: function(){
      return(
        <div>
          <button type="button" id="btnAdd" onClick={this.overlayAdd}>Add a New Recipe</button> 
            <div id="overlay">
             <div>
                <form > 
                 <p>Add a new recipe.</p>
                 Recipe Title: <input type="text" name="title" id="title" /><br/>
                 Ingredients: <input type="text" name="ingredients" id="ingredients" /><br/>
               <button type="button" className="normalBtn" onClick={this.exposeAddRecipe}>Save</button>
               </form>
               <p>Click here to <a href='#' onClick={this.overlayAdd}>close</a></p>
            
              </div>
            </div> 
          </div>
          );
         }
  });

var Footer = React.createClass({
    render() {
    return (
      <footer>
        <div id="containerfooter">
          <p>Written by <a href="http://codepen.io/profaneVoodoo/full/dXBJzN/">John Gillespie</a> for FreeCodeCamp Campers. Happy Coding!</p>
        </div>
      </footer>
    );
  }
  });


var MyApp = React.createClass({  
  render: function() {
    return(
      <div className = "mainDiv">
          <div className="titleDiv">
        <h1>Recipe Box</h1>
            
           <GenerateRecipesFromList />
           <Footer />
          </div>        
        </div>
    );
  }, 
}); 

ReactDOM.render(
  <MyApp />,
  document.getElementById('Recipes')
);

When using createClass (ES5) or class (ES6) to create a React component, this.setState() will update the state on the specific component it is called on. So you are updating the state on your form input/button component rather than your GenerateRecipesFromList component.

One way of handling this is to include a function in the props passed to your AddButton component that is bound to your GenerateRecipesFromList component. It can be called to update the correct state.

I rough sketch may look like this:

class BigComp extends React.Component {
  function constructor() {
    this.handleState = this.handleState.bind(this);
  }
  function handleState(newState) {
    this.setState(Object.assign({}, this.state, newState));
  }

  function render() {
    return(<div><ButtonComp handleState={this.handleState}></div>);
  }
}

const ButtonComp = function(props) {
  return(
    <button onClick={props.handleState}></button>
  );
};

In the above example I am using the preferred ES6 style rather than createClass. The ES6 does not “auto-bind” this, so in the constructor function I am manually binding this.

I also use Object.assign() to create a new object literal to pass to this.setState(). In React, you never want to mutate your this.state directly. Oh, and ButtonComp is what is called a state-less functional component. It has no state. It is idea for most of your components. Only a few components usually need to keep state.

This may help or merely add to the confusion!

OK I think I got it figured out now (with some extra help from StackOverflow)

I defined a function that makes setState happen in the parent component, then passed that function as a prop to the child component. The child component uses this prop/function to call setState within the parent and pass to that function the new data for the new state.

1 Like