0

Using Gatsby, I have a custom component InvertedSection that changes the class of the HTML element when the section is being scrolled through, allowing for me to change the header style. That is, when the top of the inverted section element reaches the top of the window and until the bottom of the inverted section element reaches the top of the window, the header is transparent. I accomplish this using Redux state.

In my component invertedSection.js I toggle the state locally - to limit Redux calls only on local state change - as well as using Redux createStore.js. Then in layout.js I use the Redux state to set the HTML className using React helmet.

My issue is twofold:

  1. when I navigate from a page that uses InvertedSection component to a page that does not use this component, the Redux state invertedHeader is set to true, and
  2. if I have multiple InvertedSection sections on a single page, the state change only works for the first section, not any subsequent inverted sections.

I think the issue with (1) above is that the page scrolls to the top, where the InvertedSection is located, before navigating to the new page, thus causing invertedHeader to be true when loading the new page. But I cannot find how to force redux to reset this state on every route change.

Thoughts? Everything works if I navigate to/from pages that both contain the InvertedSection so perhaps I need to call the scroll handler on every page? OR at least trigger the scroll position check and state change? Redux is a bit new to me so this can be a bit confusing.

Thanks!

# Component
# /components/invertedSection.js

import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'

const InvertedSection = ({ invertHeader, revertHeader, className, children }) => {
  let targetSection = React.createRef()

  const [isViz, setIsViz] = useState(false)

  useEffect(() => {
    handleScroll()
    window.addEventListener('scroll', handleScroll, true)
    return () => {
      window.removeEventListener('scroll', handleScroll, true)
    }
  })

  const handleScroll = () => {
    if (targetSection.current) {
      const rect = targetSection.current.getBoundingClientRect()
      if (rect && rect.top <= 0 && rect.bottom >= 0) {
        if (!isViz) {
          setIsViz(true)
          invertHeader()
          console.log('header invert')
        }
      } else {
        if (isViz) {
          setIsViz(false)
          revertHeader()
          console.log('header revert')
        }
      }
    }
  }

  return (
    <section ref={targetSection} className={className}>
      {children}
    </section>
  )
}

InvertedSection.propTypes = {
  invertHeader: PropTypes.func.isRequired,
  revertHeader: PropTypes.func.isRequired,
}

const mapStateToProps = ({ invertedHeader }) => {
  return { invertedHeader }
}

const mapDispatchToProps = dispatch => {
  return {
    invertHeader: () => dispatch({ type: `INVERTHEADER` }),
    revertHeader: () => dispatch({ type: `REVERTHEADER` }),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(InvertedSection)
# Redux
# /state/createStore.js

import { createStore as reduxCreateStore } from 'redux'

const reducer = (state, action) => {
  switch (action.type){
    case `INVERTHEADER`:
      return Object.assign({}, state, {
        invertedHeader: true,
      })
    case `REVERTHEADER`:
      return Object.assign({}, state, {
        invertedHeader: false,
      })
    default:
      break
  }
  return state
}

const initialState = {
  invertedHeader: false,
}

const createStore = () => reduxCreateStore(reducer, initialState)

export default createStore
# Layout
# /components/layout.js

import React, { useEffect } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { useStaticQuery, graphql } from 'gatsby'
import { ThemeProvider } from 'styled-components'

import theme from '../styles/theme'
import Helmet from 'react-helmet'
import GlobalStyle from '../styles/global'
import SiteHeader from './siteHeader/siteHeader'
import SiteFooter from './sitefooter/siteFooter'

const Layout = ({ children, invertedHeader }) => {
  const data = useStaticQuery(graphql`
    query {
      site {
        ...siteMeta
      }
    }
  `)

  return (
    <ThemeProvider theme={theme}>
      <Helmet>
        <html className={`${invertedHeader ? 'inverted-header' : ''}`} />
      </Helmet>
      <GlobalStyle />
      <SiteHeader
        siteTitle={data.site.siteMetadata.title}
      />
      {children}
      <SiteFooter />
    </ThemeProvider>
  )
}

Layout.propTypes = {
  children: PropTypes.node.isRequired,
  invertedHeader: PropTypes.bool.isRequired,
}

const mapStateToProps = ({ showNav, invertedHeader }) => {
  return { showNav, invertedHeader }
}

export default connect(mapStateToProps)(Layout)
# Index
# /pages/index.js

import React from 'react'
import Layout from '../components/layout'
import InvertedSection from '../components/section'

export default ({ data }) => {
  return (
    <Layout>
      <InvertedSection>
        This section uses an inverted header state
      </InvertedSection>
      <section>
        This section uses normal header state
      </section>
    </Layout>
  )
}
MonkishTypist
  • 123
  • 2
  • 8
  • Have you looked into using an [Intersection Observer](https://developers.google.com/web/updates/2016/04/intersectionobserver)? Its built into modern browsers and may solve your issues – Bill Metcalf Jan 27 '20 at 21:03
  • @BillMetcalf thanks and yes, I started looking into this recently but didn't get very far based on documentation I found. The main issue I found is how to detect visibility _from the top_ and not the bottom. But it looks like [I'm not the only one...](https://stackoverflow.com/questions/46478202/how-do-i-know-the-intersectionobserver-scroll-direction) – MonkishTypist Jan 27 '20 at 23:32

0 Answers0