Tyler Zika
Tyler Zika

Reputation: 1224

unsure why state is changing

I have a button in a child component for debugging purposes that prints the current state in its parent component. When it prints, that is how I want the state to be. When I hit another button in the same child component, the state changes in the parent, by removing a few properties.

Notice how the Id and SEO_CSEO_Survey_Questions__r properties are now missing.

Child Component

import React, { Component } from 'react';
import { Link } from 'react-router';

class Index extends Component {

  constructor(props){
    super(props)

    this.state = {
      selected: 'created'
    }

    this.updatePreview = this.updatePreview.bind(this);
    this.renderEditArea = this.renderEditArea.bind(this);
  }

  isActive(value) {
      return 'slds-tabs--default__item '+((value===this.state.selected) ?'slds-active':'');
  }

  updatePreview(e){
    const updatedPreview = {
      ...this.props.selectedPreview,
      [e.target.name]: e.target.value
    };
    this.props.update(updatedPreview)
  }

  // determines which type of edit area should display
  // survey settings or question
  renderEditArea() {
    let selected = this.props.selectedPreview;
    let hasNameKey = "Name" in selected;
    if(hasNameKey){
      return (
        <div>
          <input
            onChange={(e) => this.updatePreview(e)}
            name="Name"
            type="text"
            className="slds-input"
            value={selected.Name ? selected.Name : ''}
          />
          <input
            onChange={(e) => this.updatePreview(e)}
            name="SEO__Welcome_Text__c"
            type="text"
            className="slds-input"
            value={selected.SEO__Welcome_Text__c ? selected.SEO__Welcome_Text__c : ''}
          />
        </div>
      )
    }else {
      return (
        <input
          onChange={(e) => this.updatePreview(e)}
          name="SEO__Question__c"
          type="text"
          className="slds-input"
          value={selected.SEO__Question__c ? selected.SEO__Question__c : ''}
        />
      )
    }
  }

  render() {
    return (
      <div className="slds-size--1-of-1 slds-medium-size--4-of-5 slds-large-size--4-of-5">
        <div className="slds-tabs--default">
          <h2 className="slds-text-heading--small slds-p-top--x-small" style={{position: "absolute"}}>
            <button onClick={this.props.addQuestion} className="slds-button slds-button--icon slds-p-left--xx-small" title="add sur">
              <svg className="slds-button__icon slds-button__icon--medium" aria-hidden="true">
                <use xlinkHref={addIcon}></use>
              </svg>
              <span className="slds-assistive-text">Add Question</span>
            </button>
          </h2>
          <ul className="slds-tabs--default__nav" style={{justifyContent: "flex-end"}}>
            <Link to="/"className="slds-button slds-button--neutral">Back</Link>
            <button onClick={this.props.save} className="slds-button slds-button--brand">Save</button>
            <button onClick={this.props.currentState} className="slds-button slds-button--brand">Current State</button>            
          </ul>
        </div>
        <div className="slds-grid slds-wrap slds-grid--pull-padded">
          {this.renderEditArea()}
        </div>
      </div>
    );
  }
}

export default Index;

Parent

import React, { Component } from 'react';
import { getQuestions, addQuestion, deleteQuestion, newSurveyQuestions, updateSurveyQuestions, EMPTY_SURVEY } from './helpers';

import SideBar from './survey/SideBar';
import MainArea from './survey/Index';

class Survey extends Component {

  constructor(props) {
    super(props)

    this.state = {
      survey: [],
      selectedPreview: []
    }

    this.componentWillMount = this.componentWillMount.bind(this);
    this.save = this.save.bind(this);
    this.setSelectedPreview = this.setSelectedPreview.bind(this);
    this.currentState = this.currentState.bind(this);
  }

  // if the url is `/survey/new`, create an empty survey
  // to save for later.
  // else if there is an id in the url, load the survey and questions
  componentWillMount(){
    if(this.props.pathname === "/survey/new") {
      this.setState({
        survey: EMPTY_SURVEY,
        selectedPreview: EMPTY_SURVEY[0]
      })
    } else if (this.props.params.surveyId){
      getQuestions(this.props.params.surveyId).then(survey => {
        // 'survey' contains all the questions
        this.setState({
          survey,
          selectedPreview: survey[0]
        });
      });
    }
  }

  currentState() {
    console.log('clicking Current State');
    console.log(this.state.survey[0]);
  }

  // saves a new survey with associated newly created questions
  // or saves an existing survey with associated questions
  save() {
    console.log('clicking Save');
    console.log(this.state.survey[0]);
    // if the url is set to survey/new
    // save new survey with associated newly created questions
    if(this.props.pathname === "/survey/new") {
      newSurveyQuestions(this.state.survey).then( id => {
        this.context.router.transitionTo(`/survey/id/${id}`);
      })

    // else update survey and questions
    } else {
      updateSurveyQuestions(this.state.survey);
    }
  }

  // sets selectedPreview for the entire component and
  // its children
  setSelectedPreview(selectedPreview) {
    this.setState({selectedPreview});
  }

  render() {
    return (
      <div className="slds-grid slds-wrap slds-grid--pull-padded">
        <SideBar
          survey={this.state.survey}
          setSelectedPreview={this.setSelectedPreview}
          deleteQuestion={this.deleteQuestion}

        />
        <MainArea
          addQuestion={this.addQuestion}
          selectedPreview={this.state.selectedPreview}
          update={this.update}
          save={this.save}
          currentState={this.currentState}
        />
      </div>
    );
  }
}

Survey.contextTypes = {
  router: React.PropTypes.object
}

export default Survey;

help function

export function updateSurveyQuestions(survey) {
  // create proper url for update request
  const requestURL = `${URL + survey[0].attributes.url}`;

  // save questions for later update requests
  const questions = survey[0].SEO__CSEO_Survey_Questions__r.records;

  let s = [...survey];

  // remove properties for proper body format
  delete s[0].Id;
  delete s[0].SEO__CSEO_Survey_Questions__r;
  delete s[0].attributes;

  axios.patch(requestURL, s[0], API_TOKEN);

  questions.forEach( question => {
    // save request url for later
    let requestURL = `${URL + question.attributes.url }`;

    // remove properites for proper body format
    delete question.attributes;
    delete question.Id;

    axios.patch(requestURL, question, API_TOKEN);
  })
}

When I removed all the code in save(), except for the console.log's, it prints as expected.

Upvotes: 0

Views: 88

Answers (1)

Felix Kling
Felix Kling

Reputation: 817228

tl;dr: Your code is working fine. What you are seeing is a consequence of how objects and the console work. Do

console.log(this.state.survey[0].Id);

instead to see that the property does actually exist.

See also Is Chrome's JavaScript console lazy about evaluating arrays?


When I removed all the code in save(), except for the console.log's, it prints as expected.

That seems to suggest that that code is changing the object. E.g. updateSurveyQuestions or newSurveyQuestions might remove these properties.

You have to keep in mind that the output you see in the console is computed at the moment you expand the properties, not at the moment console.log is called. In fact, the little i icon next to Object tells you that. When you hover over it says:

Value below was evaluated just now

Here is a simplified example of what you are experiencing: Expand the object and notice that it is empty even though we removed it after calling console.dir (open your browser's console):

var obj = {foo: 42};
console.dir(obj);
delete obj.foo;


Ideally updateSurveyQuestions or newSurveyQuestions would not mutate the object directly, but rather clone it, make the necessary changes to the clone and then update the components state (if desired). E.g. a simple way to clone the object is via Object.assign:

var copy = Object.assign({}, this.state.survey[0]);

Upvotes: 1

Related Questions