4

I'm having difficulty utilizing an active state between two sibling components. I have NavComponent.jsx & HeaderComponent.jsx of which both render to different regions in the DOM.

I have a hamburger button that toggles the active state so it turns into an X while setting the menu state to active as well for the navigation. I was tasked with changing the interaction of the menu to push the content aside when the menu opens which meant I needed to break the header and navigation out into different components in the DOM. Now the active state works independently of each other when I'd like them to work together.

Someone told be about using a Redux Store but couldn't get that to work either.

Help would be very much appreciated.

NavComponent.jsx

import React from 'react';

const Navigation = (props) => (
  <nav className={'navigation' + (props.active ? ' slide-in' : '')}>
    <ul className="nav">
      {
        props.items.map(
          (item, idx) => {
            return (
              <NavigationLink key={idx} href={item.href} text={item.text} clickHandler={props.clickHandler} />
            );
          }
        )
      }
    </ul>
  </nav>
);

export default class NavComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      active: false
    };
    this.navItems = [
      {
        href: '/app/page1',
        text: 'PAGE 1'
      },
      {
        href: '/app/page2',
        text: 'PAGE 2'
      },
      {
        href: '/app/page3',
        text: 'PAGE 3'
      }
    ];
  }

  handleClick (e) {
    const viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    if (viewportWidth <= 767) {
      this.setState({ active: !this.state.active });
    }
  }

  render () {
    return (
        <Navigation active={this.state.active} items={this.navItems} clickHandler={this.handleClick.bind(this)} />
    );
  }
}

HeaderComponent.jsx

import React from 'react';
import Logo from '../img/logo.png';
import Logo2x from '../img/logo@2x.png';
import Logo3x from '../img/logo@3x.png';

const HamburgerToggle = (props) => (
  <button className={'hamburger hamburger--squeeze' + (props.active ? ' is-active' : '')} onClick={props.clickHandler} type="button">
    <span className="hamburger-box">
      <span className="hamburger-inner"></span>
    </span>
  </button>
);

const BrandLogo = () => (
  <a href="/app/page1" className="logo-link">
    <img width="92" height="29" src={Logo} srcSet={Logo + ' 1x, ' + Logo2x + ' 2x, ' + Logo3x + '3x'} alt="Logo" className="logo" />
  </a>
);

export default class HeaderComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      active: false
    };
  }
  handleClick (e) {
    const viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    if (viewportWidth <= 767) {
      this.setState({ active: !this.state.active });
    }
  }
  render () {
    return (
      <div>
        <HamburgerToggle active={this.state.active} clickHandler={this.handleClick.bind(this)} />
        <BrandLogo />
      </div>
    );
  }
}
  • So basically you want to render `NavComponent` and `HeaderComponent` and when you do an action in one, it affects the data or the state of the other? – petithomme Aug 02 '17 at 17:32
  • I would put these components as children to a parent component which would be in charge of the sibling communication using the parent's state as a base... call it SideBar or something like that. If you are clueless as to how to implement this, do a small scale example: make a class called Parent, and two others called Brother and Sister. You can test how the state communication behaves by mocking a simple user experience. Then you can observe the behavior which you can use to implement in your actual app. – AGE Aug 02 '17 at 17:35
  • @AGE Unfortunately the parent concept isn't possible at this stage because of how they are assigned in the DOM/HTML. They really do need to be two separate components. I've tried the parent method and realized I couldn't assign it to different locations. – Dave Bergschneider Aug 02 '17 at 17:39
  • @petithomme that is correct. – Dave Bergschneider Aug 02 '17 at 17:39
  • 1
    The use of a redux **store** would do just that then. An action made by one component would update the new `state` or `value` in the **store**. And when this new `state` or `value` is updated, the components that use that `state` or `value` would re-render and therefore update. – petithomme Aug 02 '17 at 17:42
  • The hardest part about using a redux store is creating the **store** and connecting your components to it. Once you're through that, it's pretty easy to use. – petithomme Aug 02 '17 at 17:43
  • 1
    I agree with the use of a store, I refrained from marking this as a duplicate until @DaveBergschneider agreed that the use of a parent component is not an option, see: https://stackoverflow.com/a/38904385 – AGE Aug 02 '17 at 18:09
  • Possible duplicate of [How to make a shared state between two react components?](https://stackoverflow.com/questions/38901106/how-to-make-a-shared-state-between-two-react-components) – AGE Aug 02 '17 at 18:10
  • Where would that store code live? Would it be in one of the two components or both? – Dave Bergschneider Aug 02 '17 at 18:21
  • @DaveBergschneider I like this answer to a react/redux folder implementation it's simple: https://stackoverflow.com/q/29945230/1046690 – AGE Aug 02 '17 at 18:23
  • I'm failing to get redux to work :( Probably not updating the store correctly. – Dave Bergschneider Aug 02 '17 at 18:35
  • Could really use a working example here – Dave Bergschneider Aug 02 '17 at 18:49
  • Unfortunately, I think it would be too long to explain how to setup your redux store here. You can read this [documentation](http://redux.js.org/docs/basics/Store.html). The instructions are pretty clear. Just create the instance of your store, `Provide` it to your entire application and use the functions `connect()`, `mapStateToProps` and `mapDispatchToProps` to link your **Components** to it. – petithomme Aug 02 '17 at 19:01
  • The important concept to grasp is that an **action** will trigger a **reducer** that will update the instance of the **store**. Your component, let's say `NavComponent` triggers the **action** _changePage_. This action will then trigger a reducer that will change the page, let's say `return pageNumber = 2`. The variable **pageNumber** in the store will update. Since your component `HeaderComponent` had `mapStateToProps(pageNumber)`, `HeaderComponent` will re-render. – petithomme Aug 02 '17 at 19:07
  • I'll write you a quick example in an answer to show you what your components should look like. – petithomme Aug 02 '17 at 19:19

1 Answers1

2

store.js

const defaultState = {
  headerData
};

const store = createStore(rootReducer, defaultState));

export default store;

rootReducer here is a .js file where all the reducers are combined.

HeaderComponent.js

class HeaderComponent extends React.Component {

    constructor...

    handleClick...

    render...

}

function mapStateToProps (state) {
  return {
    headerData: state.headerData
  };
}

export default connect(mapStateToProps)(HeaderComponent);

NavComponent.js

class NavComponent extends React.Component {

    constructor...

    handleClick...

    render...

}

function mapDispatchToProps (dispatch) {
  return bindActionCreators(navActions, dispatch);
}

export default connect(mapDispatchToProps)(NavComponent);

In this scenario, your NavComponent has access to all the actions from your file navActions. You need to create a js file navActions for this.

You HeaderComponent is mapped to the headerData of the store when you created the instant in store.js. You need to create a .js file headerData for this.

The reducer triggered by the action called in the NavComponent will update the headerData in the store and the HeaderComponent will re-render since its mapped to it.

I strongly suggest reading the entirety of Basics in the Redux Documentation to understand how to write Actions and Reducers.

petithomme
  • 509
  • 1
  • 8
  • 29