27

I have a component that receives a prop that looks like this:

const styles = {
    font: {
        size: {
            value: '22',
            unit: 'px'
        },
        weight: 'bold',
        color: '#663300',
        family: 'arial',
        align: 'center'
    }
};

I'm trying to update the align property, but when I try to update the object, I wind up replacing the whole object with just the align property.

this is how I'm updating it:

const { ...styling } = styles;
const [style, setStyle] = useState(styling);

return (
        <RadioButtonGroup
            onChange={(event) => {
                setStyle({ ...style, font: { align: event.target.value } });
                console.log(style);
            }}
        />);

When I console.log style I just get {"font":{"align":"left"}} back. I expected to see the whole object with the updated value for align. I'm new to destructuring so what am I doing wrong here?

codemon
  • 1,456
  • 1
  • 14
  • 26

6 Answers6

46

You need to use spread syntax to copy the font object properties too. Also while trying to update current state based on previous, use the callback pattern

<RadioButtonGroup
  onChange={(event) => { 
    setStyle(prevStyle => ({
        ...prevStyle,
        font: { ...prevStyle.font, align: event.target.value }
    }));
    console.log(style);
  }}
/>
Eric Tucker
  • 6,144
  • 1
  • 22
  • 36
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • I tried the solution, it is updating the object ok but I keep getting the following error in console.log ------- react-dom.development.js:506 Warning: calculateChangedBits: Expected the return value to be a 31-bit integer. Instead received: undefined in App------- Can you please assist in answer? – Ahmad Al-Baqawi Jul 18 '19 at 22:52
8

This is your mistake

setStyle({
    ...style,
    font: { align: event.target.value } // This code replace the font object
});

To preserve the all font object values, you can do like this

const onChange = (event) => {
    const s = {...style};
    s.font.align = event.target.value;
    setStyle(s);
}

Or

const onChange = (event) => {
    setStyle({ 
        ...style,
        font: {
            ...style.font, // Spread the font object to preserve all values
            align: event.target.value
        }
    });
}
Tolotra Raharison
  • 3,034
  • 1
  • 10
  • 15
  • I tried all answers and this was the only one that updated the object property without replacing the entire object. Thanks tolotra ;) – Juan David Arce Apr 16 '20 at 20:31
  • @tolotra In the object copy line `const s = {...style};` why is it declared using `const` and not `let` ? Shouldn't the code fail when you try to modify the property of a const object? – Ergin Nov 23 '21 at 21:06
  • @Ergin In JavaScript/TypeScript, a variable declared with `const` is not deep readonly like in C# or Java, it's only affect the delared variable, if I want to reassign value to my variable `s`, (like this: s = "a new object with a same shape or whatever you want"), it doesn't works. But if I want to reassign a value for a property of `s`, it works, like I did before. – Tolotra Raharison Nov 24 '21 at 14:49
  • Understood ok thanks for the explanation re: how JS defines `const`, appreciate it! – Ergin Nov 24 '21 at 20:17
5

If you have multiple values in a nested object, try this method below:

setPost({
  ...post,
  postDetails: {
    ...post.postDetails,
    [event.target.name]: event.target.value,
  },
});
Mejan
  • 918
  • 13
  • 18
0

you can update the styles in this manner

onChange={(event) => {
    const s = {...styles};
    s.font.align = event.target.value;
    setStyle(s);
    console.log(style);
}}
Ali Faris
  • 17,754
  • 10
  • 45
  • 70
-1

Your code for default useState is invalid. You should write like below for default useState:

  const { ...styling } = styles;
  const [style, setStyle] = useState({ styling }); // styling should be use in {}

return (
        <RadioButtonGroup
          onChange={event => {
            setStyle({
              ...styling,
              font: { ...styling.font, align: event.target.value }
            });
            console.log(style);
          }}
        />);

Demo: check this demo with console.log.

Edit awesome-diffie-pflqz

Mohammadreza Khedri
  • 2,523
  • 1
  • 11
  • 22
  • Thanks, but this throws the following error: `This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property 'target' on a released/nullified synthetic event. This is set to null.` It also shows the same error in your codesandbox link. – codemon Jun 28 '19 at 08:27
  • @codemon Sorry, I changed demo after insert in answer. check demo again. – Mohammadreza Khedri Jun 28 '19 at 08:33
-1

First: const { ...styling } = styles; is the same as: const styling = styles;

But you may skip that altogether, just use [...] = useState(styles)

setStyle() accepts a function which receives the current state, in your case style. So you may use the spread operator ... to create new object and add values to it.

setStyle(style => ({ ...style, font: { ...style.font, align: event.target.value } ) ) );

Above code uses Arrow function, this could also be written as a normal function, but probably easier to understand:

setStyle( function (style) {
    return  {
        ...style,
        font: {
            ...style.font,
            align: 'center'
        }
    }
});

Your console.log in setStyle displays current style, as changed style will be seen on next render. So I've moved the console.log before return.

const [style, setStyle] = useState(styles);

console.log(style);

return (
        <RadioButtonGroup
            onChange={(event) => {
                setStyle(style => ({ ...style, font: { ...style.font, align: event.target.value } ) ) );
            }}
        />);

Here are some links to help you understand all that: