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:
- when I navigate from a page that uses
InvertedSection
component to a page that does not use this component, the Redux stateinvertedHeader
is set totrue
, and - 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>
)
}