Mighty React coming your way
All our smart logic will be placed in the Header component so let’s build it like this:
const Header = ({ children }) => {
const [headers] = useContext(HeaderContext)
return (
<HeaderStyled>
{headers &&
headers.map((header, index) => (
<div
key={uid(index)}
className="header_wrapper"
>
<div className="header_inner">
{cloneElement(children)}
</div>
</div>
))}
</HeaderStyled>
)
}
This Header component can now replace our HTML from the start. Children prop makes it around any component, in our case Logo:
<Header>
<Logo />
</Header>
Section component - the one that alerts Header to change its color - is pretty straightforward:
{sections &&
sections.map((section, index) => {
const { type, title, subtitle, backgroundColor } = section
return (
<div key={uid(section)}>
<Section
type={type}
title={title}
subTitle={subtitle}
backgroundColor={backgroundColor}
/>
</div>
)
})
}
All header and section data will be stored in context so that we can access it from anywhere. Both have references which will be used to do the color magic.
Header data example:
const headers = [
{
id: 0,
ref: 'header_0',
color: '#FFFFFF'
},
{
id: 1,
ref: 'header_1',
color: '#000000'
},
{
id: 2,
ref: 'header_2',
color: '#FFFFFF'
},
{
id: 3,
ref: 'header_3',
color: '#430098'
}
]
…and the section data example with everything needed for each section, plus background colors:
const sections = [
{
id: 0,
ref: 'section_0',
title: 'Making interactions between humans
and computers a joy.',
type: 'memory',
backgroundColor: '#430098'
},
{
id: 1,
ref: 'section_1',
title: 'We are the H in Human-Computer Interaction.',
type: 'pppp',
subtitle:
'We make websites, mobile apps,
content management systems, API’s.
Anything that allows interaction
between humans and computers.',
backgroundColor: '#fdc92a'
},
{
id: 2,
ref: 'section_2',
title: 'Hey you!',
type: 'pppp',
subtitle:
'We're Human, an interface company
old enough to buy booze. We've pushed
pixels above and beyond many times
through the years, enough that
they've even put us in a Japanese book.',
backgroundColor: '#fb3e31'
},
{
id: 3,
ref: 'section_3',
type: 'reminder',
subtitle:
'We believe websites can run faster,
remotes can look better and parmiggiano
reggiano can't be put on seafood.
If you don't agree, find another company.',
backgroundColor: '#f5f5f5'
}
]
Now we can combine our header and sections on a page - we’ll call it Dashboard:
const Dashboard = () => {
const [headers, sections] = useContext(HeaderContext)
const sectionRefs = useRef(sections.map(() => createRef()))
return (
<DashboardStyled>
<Header sectionRefs={sectionRefs}>
<Logo />
</Header>
{sections &&
sections.map((section, index) => {
const { type, title, subtitle, backgroundColor } = section
return (
<div
key={uid(section)}
ref={sectionRefs.current[index]}
>
<Section
type={type}
title={title}
subTitle={subtitle}
backgroundColor={backgroundColor} />
</div>
)
})}
</DashboardStyled>
)
}
export default Dashboard
We’re taking refs of all sections and sending them to the Header component through prop. We need those for their top and bottom positions.
Back to the start
Now let’s go back to work on our Header component.
We want to take the refs of the headers and all the sections to find out what their position is and to have a knowledge of those positions while scrolling.
For the header variables we’ll need these to start with:
const headerRef = useRef()
const headerBounds = headerRef?.current?.getBoundingClientRect()
const headerBottomPosition = headerBounds?.bottom
const headerHeight = headerBounds?.height
Position of the sections will change with the scroll (because we are adding window scroll to it) so let’s put that calculation in useEffect that will depend on the scroll.
const [sectionPositions, setSectionPositions] = useState([])
const [windowOffset, setWindowOffset] = useState(0)
useEffect(() => {
const sectionTops = sectionRefs.current.map(ref => ref.current.getBoundingClientRect().top)
setSectionPositions(sectionTops)
const sectionBottomsWithOffset = sectionRefs.current.map(
ref => ref.current.getBoundingClientRect().bottom + windowOffset
)
const handleScroll = () => {
setWindowOffset(window.scrollY)
}
// Adding event listener on mount and we are handling our scroll on mount
window.addEventListener('scroll', handleScroll)
// Important to do a cleanup. Whenever this component gets unmounted
// this return will be called and remove the event listener for us.
// We dont want to always read our event listener.
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [windowOffset])
All section bottom positions are placed in an array, so later we can loop through them.