1

I have the following file, LookupPage.jsx and AccountDetails.jsx.

In LookUp

this.updateCustomer = (customer) => {

    if(JSON.stringify(customer.address) !== JSON.stringify(this.state.activeAccount.customer.address)) {
            console.log('address changed');
            customer.update_address = true;
            customer.address.source = 'user';
        }
    return fetch(
        `${API_ENDPOINT}/customer/${customer.id}/`,
        {
            method: 'PATCH',
            headers: {
                'Authorization': 'Token ' + this.props.session_token,
                    'Content-Type': 'application/json',
            },
            body: JSON.stringify(customer),
        }
    ).then(restJSONResponseToPromise).then(responseJSON => {
        if(responseJSON.results){
            console.log('update customers client side.')
        }
    }, clearSessionIfInvalidToken(this.props.clearSession));
};

<AccountsDetailModal
    show={this.state.showAccountDetail}
    close={this.toggleAccountDetail}
    customer={this.state.activeAccount.customer}
    updateCustomer={this.updateCustomer}
/>

In side AccountDetails

this.onChangeAddress = (e) => {
    const customer = {...this.state.customer};
    const address = customer.address;
    address[e.target.name] = e.target.value;
    customer.address = address;
    this.setState({customer, errors: {
        ...this.state.errors,
        [e.target.name]: [],
    }});
};

this.saveCustomer = () => {
    this.setState({postDisable: true});
    const errors = this.getFormErrors();
    const hasErrors = !every(errors, (item) => !item.length);
    if(!hasErrors){
        this.props.updateCustomer(this.state.customer);
    } else {
        sweetAlert('Error!', 'Form is invalid.', 'error');
    }
    this.setState({postDisable: false});
};

this.componentDidMount = () => {
    this.setState({customer: this.props.customer});
}

When I am updating the customers address, it is updating active accounts address, so it seems like it is being passed by reference. What I want to happen is only update the customer address if the address was changed/different from the original. How would I modify my code to do this?

rak1n
  • 671
  • 1
  • 8
  • 17
  • You cannot change how arguments are passed in JS (objects -> "by reference"). Are you by chance looking for mutable vs. immutable / pure functions? – le_m Jun 02 '17 at 17:17
  • Possible duplicate of [Is JavaScript a pass-by-reference or pass-by-value language?](https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language) – random_user_name Jun 02 '17 at 17:25
  • This has nothing to do with React, and everything to do with the way javascript handles variables. Please reference the link above about "pass-by-reference or pass-by-value" for more information. – random_user_name Jun 02 '17 at 17:26
  • My best guess is I have to update to componenetDidMount, so when I am setting the customer, it will be a copy of the props object and not the original props object. How do I do that? I tried doing this, but still no luck. this.setState({customer: {...this.props.customer, update_address: false}}); – rak1n Jun 02 '17 at 17:31

2 Answers2

3

You can pass any object by value in JS (whether you're using React or not) by passing:

JSON.parse(JSON.stringify(myObject))

as an argument instead of the object itself.

Essentially this will just clone the object and pass a copy of it, so you can manipulate the copy all you want without affecting the original.

Note that this will not work if the object contains functions, it will only copy the properties. (In your example this should be fine.)

Joey
  • 10,504
  • 16
  • 39
  • 54
  • 2
    It would be REALLY great if you explained in more detail WHAT is happening, WHY it is happening, and the fact that this has nothing to do with React, and everything to do with the way JS handles primitives / objects when passing / modifying... – random_user_name Jun 02 '17 at 17:25
  • This passes a new object (in many cases but not always a copy) per reference - not very robust / not recommended – le_m Jun 02 '17 at 17:33
  • Unfortunately it also wont work in many other cases, e.g. for NaN, Infinity, Symbol etc. – le_m Jun 02 '17 at 17:45
  • @cale_b Updated to explain in more detail – Joey Jun 02 '17 at 19:17
0

I am going to put my two cents here:

First of all, this isn't really specific to React and is more of a JS related question.

Secondly, setting props against internal state is considered to be a bad practice when it comes to react. There's really no need to do that given your particular scenario. I am referring to

this.setState({customer: this.props.customer});

So, coming to your problem, the reason you are having reference issues is because you are mutating the original passed in object at certain points in your code. For instance, if I look at:

this.updateCustomer = (customer) => {

    if(JSON.stringify(customer.address) !== JSON.stringify(this.state.activeAccount.customer.address)) {
            console.log('address changed');
            customer.update_address = true;
            customer.address.source = 'user';
        }
};

You are mutating the original props of the argument object which is very likely to be passed around in other methods of your component. So, to overcome that you can do:

const updatedCustomer = Object.assign({}, customer, {
  update_address: true
});

And you can pass in updatedCustomer in your API call. Object.assign() will not perform operation on the passed in object but will return a new object so you can be sure that at any point in your app you are not mutating the original object.

Note: Object.assign would work on plain object and not a nested one. So, if you want to achieve something similar that would work on nested object properties too, you can use lodash merge.

Umair Sarfraz
  • 5,284
  • 4
  • 22
  • 38