Skip to content
Advertisement

Set initial class variable from axios request in React

When i call this function

getQuestions = () => {
    this.setState({ loading: true })
    const { data } = this.props

    axios
        .get(data.questions)
        .then((res) => {
            this.setState({
                loading: false,
                questions: res.data,
            })

            this.initialQuestions = res.data
        })
        .catch((err) =>
            this.setState({
                loading: false,
                questions: [],
            })
        )
}

it updates the array questions in state and array initialQuestions variable in constructor. The state questions represents the values form inputs. The inputs are handled in child component with this code

onChange = (e) => {
    const { hasChanged, setQuestions } = this.props

    // Update questions
    let questions = this.props.questions
    questions[e.target.getAttribute('data-id')][e.target.name] =
        e.target.value
    setQuestions(questions)
}

setQuestions is passed in props as setQuestions={(state) => this.setState({ questions: state })} So when i change the value of inputs the onChange function is called and it changes the parent component questions in state. But the parent variable this.initialQuestions also is being changed to the questions value from state, but I don’t know why

Edit:

That’s the code you should be able to run

const { Component } = React;
const Textarea = "textarea";

const objectsEquals = (obj1, obj2) =>
    Object.keys(obj1).length === Object.keys(obj2).length &&
    Object.keys(obj1).every((p) => obj1[p] === obj2[p])

class QuestionList extends React.Component {
    static propTypes = {
        questions: PropTypes.array,
        removeQuestion: PropTypes.func.isRequired,
        hasChanged: PropTypes.func.isRequired,
        setQuestions: PropTypes.func.isRequired,
    }

    constructor(props) {
        super(props)

        this.questions = props.questions

        this.onChange = this.onChange.bind(this)
    }

    onChange = (e) => {
        const { hasChanged, setQuestions } = this.props

        // Update questions
        let questions = this.props.questions
        questions[e.target.getAttribute('data-id')][e.target.name] =
            e.target.value
        setQuestions(questions)

        if (hasChanged && this.questions.length > 0) {
            // array of booleans, true if object has change otherwise false
            const hasChangedArray = this.props.questions.map(
                (_, index) =>
                    !objectsEquals(
                        this.questions[index],
                        this.props.questions[index]
                    )
            )
            console.log("hasChangedArray = ", hasChangedArray)
            console.log("this.questions[0] = ", this.questions[0])
            console.log("this.props.questions[0] = ", this.props.questions[0])

            // If true in array than the form has changed
            hasChanged(
                hasChangedArray.some((hasChanged) => hasChanged === true)
            )
        }
    }

    render() {
        const { removeQuestion, questions } = this.props

        const questionList = questions.map((question, index) => (
            <div className="card" key={index}>
                <div className="card__body">
                    <div className="row">
                        <div className="col-sm-7">
                            <div className="form-control">
                                <label className="form-control__label">
                                    Question:
                                </label>
                                <input
                                    type="text"
                                    id={`question-${index}`}
                                    data-id={index}
                                    onChange={this.onChange}
                                    name="question"
                                    value={
                                        this.props.questions[index].question
                                    }
                                    className="form-control__input form control__textarea"
                                    placeholder="Pass the question..."
                                    rows="3"
                                />
                            </div>
                            <div className="form-control">
                                <label className="form-control__label">
                                    Summery:
                                </label>
                                <Textarea
                                    id={`summery-${index}`}
                                    data-id={index}
                                    onChange={this.onChange}
                                    name="summery"
                                    value={this.props.questions[index].summery}
                                    className="form-control__input form-control__textarea"
                                    placeholder="Pass the summery..."
                                    rows="3"
                                />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        ))

        return questionList
    }
}

class Questions extends React.Component {
    constructor(props) {
        super(props)

        this.initialQuestions = []

        this.state = {
            loading: true,
            questions: [],
            hasChanged: false,
        }

        this.getQuestions = this.getQuestions.bind(this)
        this.resetForm = this.resetForm.bind(this)
    }

   resetForm = () => {
        console.log("this.initialQuestions =", this.initialQuestions)
        this.setState({
            questions: this.initialQuestions,
            hasChanged: false,
        })
    }

   getQuestions = () => {
        this.setState({ loading: true })
        const { data } = this.props

        // axios
        //  .get(data.questions)
        //  .then((res) => {
        //      this.setState({
        //          loading: false,
        //          questions: res.data,
        //      })

        //      this.initialQuestions = res.data
        //  })
        //  .catch((err) =>
        //      this.setState({
        //          loading: false,
        //          questions: [],
        //      })
        //  )

       // You can't do a database request so here is some example code
       this.setState({
           loading: false,
           questions: [
               {
                   question: 'example-question',
                   summery: 'example-summery',
               },
               {
                   question: 'example-question-2',
                   summery: 'example-summery-2',
               },
           ],
       })

      this.initialQuestions = [
          {
              question: 'example-question',
              summery: 'example-summery',
          },
          {
              question: 'example-question-2',
              summery: 'example-summery-2',
          },
      ]
    }

   componentDidMount = () => this.getQuestions()

   render() {
      const { loading, questions, hasChanged } = this.state

      if (loading) return <h1>Loading...</h1>

      return (
          <form>
                <QuestionList
                    questions={questions}
                    hasChanged={(state) =>
                        this.setState({ hasChanged: state })
                    }
                    setQuestions={(state) =>
                        this.setState({ questions: state })
                    }
                />


                <button
                    type="reset"
                    onClick={this.resetForm}
                    className={`btn ${
                        !hasChanged
                        ? 'btn__disabled'
                        : ''
                    }`}
                >
                    Cancel
                </button>
                <button
                    type="submit"
                    className={`btn btn__contrast ${
                        !hasChanged
                            ? 'btn__disabled'
                            : ''
                    }`}
                >
                    Save
                </button>

          </form>
      )
   }
}

ReactDOM.render(<Questions />, document.querySelector("#root"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/prop-types@15/prop-types.min.js"></script>
<div id="root"></div>

Advertisement

Answer

Both state questions and class variable initialQuestions hold reference of res.data. Now when you update questions in onChange method, you are updating it by reference i.e directly mutating it and hence the class variable is also updated

You must not update it by reference but clone and update like below

onChange = (e) => {
    const { hasChanged, setQuestions } = this.props

    // Update questions
    let questions = this.props.questions
    questions = questions.map((question, idx) => {
        if(idx === e.target.getAttribute('data-id')) {
             return {
                ...question,
                [e.target.name]: e.target.value
             }
         }
         return question;
    });
    setQuestions(questions)
}
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement