Meeseks
Meeseks

Reputation: 28

REACT: Unable to update children props

I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.

I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.

Thank you!

class IndexPage extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      section: "",
      headerVisible: true,

    }
    this.displaySection = this.displaySection.bind(this)
  }

  displaySection(sectionSelected) {
    this.setState({ section: sectionSelected }, () => {
      this.sectionRef.current.changeSection(this.state.section)
    })

    setTimeout(() => {
      this.setState({
        headerVisible: !this.state.headerVisible,
      })
    }, 325)

    setTimeout(()=>{
      console.log('this.state', this.state)
    },500)
  }

  render() {

    return (
      <Layout>
            <Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
      </Layout>
    )
  }
}
const Header = props => (
  <header className={props.headerVisible ? 'visible' : 'invisible'}>
       <div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
              <span onClick={() => { this.props.selectSection("projects")}}>
                {" "}
                Projects
              </span>
  </header>
)

Upvotes: 0

Views: 151

Answers (2)

dbramwell
dbramwell

Reputation: 1326

There seemed to be a couple of issues with your example code:

  1. Missing closing div in Header
  2. Using this.props instead of props in onclick in span in Header

The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.

class IndexPage extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      section: "",
      headerVisible: true,

    }
    this.displaySection = this.displaySection.bind(this)
  }

  displaySection(sectionSelected) {
    this.setState({ section: sectionSelected })

    setTimeout(() => {
      this.setState({
        headerVisible: !this.state.headerVisible,
      })
    }, 325)

    setTimeout(()=>{
      console.log('this.state', this.state)
    },500)
  }

  render() {

    return (
      <div>
            <Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
      </div>
    )
  }
}

const Header = props => (
  <header className={props.headerVisible ? 'visible' : 'invisible'}>
       <div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
              <span onClick={() => { props.selectSection("projects")}}>
                {" "}
                Projects
              </span>
       </div>
  </header>
)

ReactDOM.render(
  <IndexPage />,
  document.getElementsByTagName('body')[0]
);
.visible {
  opacity: 1
}

.invisible {
  opacity: 0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Upvotes: 1

Bender
Bender

Reputation: 647

There is a markup error in your code in Header component - div tag is not closed. Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.

As @Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})

Any way here is a working example of what you trying to achieve:

// @flow
import * as React from "react";
import Header from "./Header";

type
Properties = {};

type
State = {
    section: string,
    headerVisible: boolean,
};

class IndexPage extends React.Component<Properties, State> {

    static defaultProps = {};

    state = {};

    constructor(props) {
        super(props);
        this.state = {
            section: "",
            headerVisible: true,

        };
        this.displaySection = this.displaySection.bind(this)
    }

    displaySection(sectionSelected) {
        setTimeout(
            () => this.setState(
                prevState => ({
                    section: sectionSelected,
                    headerVisible: !prevState.headerVisible
                }),
                () => console.log("Debug log: \n", this.state)
            ),
            325
        );
    }

    render(): React.Node {
        const {section, headerVisible} = this.state;

        return (
            <React.Fragment>
                <Header selectSection={this.displaySection} headerVisible={headerVisible} />
                <br/>
                <div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
            </React.Fragment>
        )
    }
}

export default IndexPage;

and Header component

// @flow
import * as React from "react";

type Properties = {
    headerVisible: boolean,
    selectSection: (section: string) => void
};

const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
    const headerRef = React.useRef(null);

    return (
        <React.Fragment>
            <header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
                <div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
                    <span onClick={() => selectSection("projects")}>Projects</span>
                </div>
            </header>
            <br/>
            <div>Header class name: {headerRef.current && headerRef.current.className}</div>
        </React.Fragment>
    );
};

export default ComponentName;

Upvotes: 1

Related Questions