Jordan Sykes
Jordan Sykes

Reputation: 71

React context API no passing on new state value to all Consumer props

I am building a site that has quite a few react components in it, but is not a SPA or a full react site. Therefore react components get pulled into pages. I have an overlay component that covers the Ui when various menu's open or dropdown's appear. The problem I have is that I have set up my context like so:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export const OverlayContext = React.createContext();

export class OverlayProvider extends Component {
    state = {
            showOverlay: true,
            test: 'default test value'
    }

    showHideOverlay = () => {
        const visible = this.state.showOverlay;
        this.setState({
            showOverlay: visible ? false : true
        });
    }

    render() {
        return (
            <OverlayContext.Provider value={{
                state: this.state,
                showHideOverlay: this.showHideOverlay
            }}>
                {this.props.children}
            </OverlayContext.Provider>
        );
    }
}

OverlayProvider.propTypes = {
    children: PropTypes.object
}

Then I pass this provider and consumer down to 2 different components:

  1. My overlay component
  2. My MyAccount component.

Here they are: Overlay:

import React from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types'

import { OverlayProvider, OverlayContext } from './../contexts/OverlayContext';


// Styling
import styled from 'styled-components'
import { colors } from '../global-styled-constants/colors';
import { mixins } from '../global-styled-constants/mixins';
import { sizing } from '../global-styled-constants/sizing';

//Animations
import { fadeIn } from '../global-styled-constants/animations';
import { fadeOut } from '../global-styled-constants/animations';

const PageOverlay = styled.div`
    ${mixins.dimensions('100%', '100%')};
    ${mixins.position('fixed', '0', '0')};
    display: block;
    background: ${colors.red};
    z-index: ${sizing.zIndex2};
    ${props => props.showOverlay ? `animation: ${fadeIn} 1s 0.3s both;` : `animation: ${fadeOut} 1.3s both;`}
`;

// the state containing the count will be fetched via the API and delivered to this component via redux
export const Overlay = () => {
    return (
        <OverlayProvider>
            <OverlayContext.Consumer>
                {(value) => (
                    <PageOverlay showOverlay={value.state.showOverlay}/>
                )}
            </OverlayContext.Consumer>
        </OverlayProvider>
    );
}

render(<Overlay/>, document.getElementById('react-overlay'))


Overlay.propTypes = {
    showOverlay: PropTypes.bool.isRequired
}

MyAccount:

import React, { Component, Fragment } from 'react';
import { OverlayProvider, OverlayContext } from '../../contexts/OverlayContext';

// Styling
import styled from 'styled-components'
import { colors } from '../../global-styled-constants/colors'
import { mixins } from '../../global-styled-constants/mixins'
import { sizing } from '../../global-styled-constants/sizing'
import { type } from '../../global-styled-constants/typography'
// import { mq } from '../../global-styled-constants/mediaQueries';

const Wrapper = styled.div`
    position: relative;
`;

const List = styled.ul`
    ${mixins.dimensions('195px', 'auto')};
    position: absolute;
    background: ${colors.lightSmoke};
    z-index: 10;
    top: 38px;
    padding: 15px;
    ${props => props.showDropdown ? `display: block;` : `display: none`};

`;

const Item = styled.li`
    padding: 15px 0;

    a {
        font-size: ${sizing.scaleh10};
        font-family: ${type.fsSanSerif};
        font-weight: ${sizing.fontWeightBold};
        font-style: normal;
        text-decoration: none;

    }

    &:hover {
        cursor: pointer;
        color: ${colors.orange};
    }
`;

const Trigger = styled.li`
    ${mixins.flex('center')};
    height: 38px;
    font-size: ${sizing.scaleh10};
    padding: 0 20px;
    position: relative;
    color: ${colors.anotherGrey};
    font-family: ${type.fsSanSerif};
    font-weight: ${sizing.fontWeightBold};
    font-style: normal;
    text-decoration: none;
    cursor: pointer;
    ${props => props.showDropdown && `background: ${colors.lightSmoke}; color: ${colors.orange}`};
`;


const accountData = {
    "loggedOut": {
        "displayCTA": "My Account",
        "links": [
            {
                "displayName": "Sign In",
                "link": "www.google.com"
            },
            {
                "displayName": "Sign up / Join",
                "link": "www.google.com"
            },
            {
                "displayName": "Register Products",
                "link": "www.google.com"
            },
            {
                "displayName": "Subscribe to Newsletter",
                "link": "www.google.com"
            }
        ]
    },
    "loggedIn": {
        "displayCTA": "{userEmail}",
        "links": [
            {
                "displayName": "My Products",
                "link": "www.google.com"
            },
            {
                "displayName": "My Wishlist",
                "link": "www.google.com"
            },
            {
                "displayName": "My Orders",
                "link": "www.google.com"
            },
            {
                "displayName": "My Profile",
                "link": "www.google.com"
            },
            {
                "displayName": "Sign Out",
                "link": "www.google.com"
            },
            {
                "displayName": "Register Products",
                "link": "www.google.com"
            },
            {
                "displayName": "Newsletter Sign up",
                "link": "www.google.com"
            }
        ]
    }
}




export default class MyAccount extends Component {
    constructor(props) {
        super(props);
        this.state = {
            showDropdown: false,
            loggedIn: false
        }
    }

    hoverAwakeMenu = () => {
        this.setState({
            showDropdown: true
        })
    }

    mouseLeft = () => {
        this.setState({
            showDropdown: false
        });
    }

    render() {
        const { showDropdown, loggedIn } = this.state;
        const data = loggedIn ? accountData.loggedIn.links : accountData.loggedOut.links;
        const displayCTA = loggedIn ? accountData.loggedIn.displayCTA : accountData.loggedOut.displayCTA;

        const itemsList = data.map((item, i) => <Item key={i}><a href={item.link}>{item.displayName}</a></Item>);

        return (
            <OverlayProvider>
                <Fragment>
                    <Wrapper onMouseLeave={this.mouseLeft}>
                        <OverlayContext.Consumer>
                            {(value) => (
                                <Trigger showDropdown={showDropdown} onMouseEnter={(e) => {this.hoverAwakeMenu(e); value.showHideOverlay(e);}}>{displayCTA}</Trigger>
                            )}
                        </OverlayContext.Consumer>
                        <List showDropdown={showDropdown}>
                            {itemsList}
                        </List>
                    </Wrapper>
                </Fragment>
            </OverlayProvider>
        );
    }
}

When I call value.showHideOverlay(e); from MyAccount.js it updates the state in the context, but it does not then forward this change to the Overlay.js where the prop 'showOverlay' styles based on this boolean. If I put a test prop in my account, it gets the change. The same thing happens if I trigger an event from Overlay.js. The props inside overlay gets updated from then state in the context, but not the one in MyAccount.js

Im guessing im doing something wrong with the multiple providers maybe but I was under the impression multiple providers was absolutely fine with the react context API.

Any direction appreciated!

Upvotes: 0

Views: 980

Answers (1)

Estus Flask
Estus Flask

Reputation: 222493

Context API relies on component hierarchy. Changes in one Provider value won't be magically available in another Provider. They are totally unrelated at this point.

In order to share a value between Consumer components, there should be single Provider that contains all Consumer. OverlayProvider is redundant at this point because this component isn't supposed to be reused.

If this is non-SPA then unrelated components should be rendered as portals under same Provider, as suggested in this answer.

Upvotes: 1

Related Questions