How do I fix "Cannot assign to read only property 'style' of object" in this React component?

24,807

You try to change property value in this line:
tab.props.style = newStyle;
but props in React are read-only and you can't manually change their values. Please check React docs for more details.
To fix it you can use React.cloneElement which allow to clone element with new properties that will be merged into existing ones:

render() {
    let childrenWithNewProps = React.Children.map(this.props.children, (el, i) => {
        let visibility = 'none';
        if (i === this.state.activeIndex) visibility = 'block';
        return React.cloneElement(el, {
               style: {
                 display: visibility
               }
           }
        )
    });

    return (
          <div style={this.props.style}>
            {this.tabHeads}
            {childrenWithNewProps}
          </div>
    );
}
Share:
24,807
Timo Ernst
Author by

Timo Ernst

Coding Architect at Audi Business Innovation JavaScript Enthusiast YouTube Coding Tutor: http://www.timoernst.tv

Updated on May 12, 2021

Comments

  • Timo Ernst
    Timo Ernst almost 3 years

    I am creating an own custom tab component. It consists of a tab header which each has a body part. When a tab header is clicked, the corresponding body's style should be set to display:block and all others to display:none.

    For some reasons I am getting this error:

    Cannot assign to read only property 'style' of object

    I understand that I am not allowed to mutate the style property manually as it seems to be read-only but how do I fix/work around this?

    Here is my code:

    Tabs.js

    import React, { Component } from 'react';
    
    class Tabs extends Component {
    
      constructor() {
        super();
        this.state = {
          activeIndex: 0,
        };
        this.tabHeads = [];
      }
    
      // Initial composition of tab heads
      componentWillMount() {
        React.Children.map(this.props.children, (el, i) => {
          const tab = el;
          const key = `tabKey${i}`;
          this.tabHeads.push((
            <div role="button" onClick={e => this.onTabClick(e, i)} key={key}>{tab.props.title}</div>
          ));
        });
      }
    
      // Called when user clicks a tab head
      onTabClick(e, i) {
        this.setState({ activeIndex: i });
      }
    
      render() {
        // Set display none or block for each tab
        React.Children.map(this.props.children, (el, i) => {
          const tab = el;
          let visibility = 'none';
          if (i === this.state.activeIndex) visibility = 'block';
          const newStyle = Object.assign({}, tab.props.style, { display: visibility });
          tab.props.style = newStyle;
        });
    
        return (
          <div style={this.props.style}>
            {this.tabHeads}
            {this.props.children}
          </div>
        );
      }
    }
    
    export default Tabs;
    

    Usage is like this:

    render() {
      const tabStyles = [
        { padding: '20px' }, // Tab 0
        { padding: '20px' }, // Tab 1
      ];
    
      <Tabs>
    
        <Tab style={tabStyles[0]} title="Tab1">
          <div>Content 1</div>
        </Tab>
    
        <Tab style={tabStyles[1]} title="Tab2">
          <div>Content 2</div>
        </Tab>
    
      </Tabs>
    }