Since the release of React 16.8, I’ve been enjoying the ability to use to create composable and reusable logic, and write fewer class components with explicit lifecycle methods. This not only leads to a more enjoyable dev experience, but (in my opinion) a more readable code base. hooks Today I am going to introduce a few hooks and use them to build a simple responsive layout. Using the Context API, we can ensure that all components have access to up-to-date window dimensions, without relying on lifecycles to attach and clean up event listeners. The Finished Product v responsive The finished code can be found on . github First Steps To begin, we will scaffold a new react app with , and add the wonderful css library: create-react-app bulma $ create-react-app my-responsive-app$ cd !$$ yarn add bulma node-sass$ yarn start That’s it for getting started. We are going to be using for some nice-looking components, but we will handle all the responsive design using React and JS. bulma On Being Responsive Handling different device sizes is pretty important from what I hear. What is responsive web design exactly? According to the internet: ( ) is an approach to that makes web pages render well on a variety of devices and window or screen sizes Responsive web design RWD web design — Wikipedia Okay! This seems like something we can handle in CSS, using relative units and media queries, right? Well, yes and no. Most of the time, CSS can handle the job if the job is simply arranging content on a page. However, sometimes, you may want to change render based on the page size, and for this, you almost definitely will need some (or other dark magic). behavior Javascript The React Way So React is all about creating reusable bits of presentation logic (components) and using them to compose views. Now that we have hooks, we can actually simplify this composition further: hooks allow us to encapsulate certain behaviors, often composed of multiple hooks, into functions that integrate directly into React’s renderer (using the new , which we won’t dive into here) to automatically update the components using them. React Fiber So? So, this old thing ( if you’ve written this in one of your React Apps): /clap class NavBar extends Component {state = { open: false } setOpen = open => this.setState({ open }) render() {return (this.state.open ?<NavMenu /> :<HamburgerButton onClick={() => this.setOpen(true)} />)}} Has now become: function NavBar() {const [open, setOpen] = useState(false)return (open ?<NavMenu /> :<HamburgerButton onClick={() => setOpen(true)} />)} If this doesn’t seem like a big deal yet, you can check out some of the cool things people have made with hooks here: https://codesandbox.io/react-hooks The point is, hooks are nice. We don’t have to type as much, and we almost never have to worry about or the status of the class properties proposal (thank you babel) again. componentDidMount/componentDidUpdate/setState Now that we know what responsive web design is (kind of), and hooks (also kind of), let’s use both of them to build something! To start, let’s implement our fundamental building block (for this article): the component. ResponsiveLayout To start, we’ll create a good old functional component (in ), that accepts three props: , , and . src/components/ResponsiveLayout/index.js renderDesktop renderMobile breakpoint import { useState, useEffect } from 'react' const ResponsiveLayout = ({ breakpoint, renderMobile, renderDesktop }) => {const [width, setWidth] = useState(window.innerWidth)useEffect(() => {const handleResize = () => {setWidth(window.innerWidth)}window.addEventListener(‘resize’, handleResize)return () => { window.removeEventListener(‘resize’, handleResize) }}, [])return (width > breakpoint ? renderDesktop() : renderMobile())} We are simply reading the window’s width when the component gets created and storing it using the hook, then establishing an event listener inside of our hook. useState useEffect useState allows us to have stateful functional components! you call and it returns a pair of . Because it returns an array and not an object, you are free to name these references however you please. Most importantly, hooks into React DOM and triggers a render when you use the function returned from it, so it works just like would for class components. useState useState(initialValue) [state, setState] useState setState this.setState useEffect will normally run its code every time a component re-renders, however it accepts a second argument, an array of variables, that inform that it should only re-run when a value inside the array is changed. When provided an empty array, it will never re-run, so it behaves exactly like ! The return value for should be a cleanup function, if required, so that it can be run before the effect is re-run, or the component is being removed from the DOM. Here we specify an anonymous function that will remove the event listener when this component is un-mounting (sound familiar? this is what we’ve been doing inside of ). useEffect useEffect componentDidMount useEffect componentWillUnmount Now this seems pretty good, but what happens if there are multiple places where things on the page need to render based on a breakpoint? We shouldn’t be setting up new event listeners for the same event every time one of these components is mounting, especially since we only have one window resize to listen for. Enter useContext One of the ways hooks have really made things better is when using the Context API. Previously, using Context required using Consumer and Provider components, and utilizing the Consumer in any component that needed access to the value. This led to devs creating Higher Order Components for providing context as props (for example, 's ) or simply very verbose code. Now, with , we no longer have to use Components to make children aware of values from Context. react-redux connect useContext Context.Consumer Let’s rewrite the above component, using Context and an App-wide . WindowDimensionsProvider In : src/components/WindowDimensionsProvider/index.js import React, { createContext, useContext, useState, useEffect } from 'react' const WindowDimensionsCtx = createContext(null) const WindowDimensionsProvider = ({ children }) => {const [dimensions, setDimensions] = useState({width: window.innerWidth,height: window.innerHeight,})useEffect(() => {const handleResize = () => {setDimensions({width: window.innerWidth,height: window.innerHeight,})}window.addEventListener(‘resize’, handleResize)return () => { window.removeEventListener(‘resize’, handleResize) }}, [])return (<WindowDimensionsCtx.Provider value={dimensions}>{children}</WindowDimensionsCtx.Provider>)} export default WindowDimensionsProviderexport const useWindowDimensions = () => useContext(WindowDimensionsCtx) This component should be invoked in , like so: src/App.js import React from 'react'import Content from './components/Content'import WindowDimensionsProvider from './components/WindowDimensionsProvider'import items from './data.json'import 'bulma/css/bulma.css' App = () (<WindowDimensionsProvider><div ="App"><Content ={items} /></div></WindowDimensionsProvider>) const => className items App export default Note: In a real world app, you should write a Higher-Order Component, for utilization with class Components; see a sample implementation . also here Don’t worry about the and , we’ll get to those later. What’s important is that the component appear higher in your app’s render tree than any component that wants to read from its for data. (This is important so React knows which context value to provide, in case you have multiple instances of the same type of rendered in your app.) Content items Provider Context Provider By doing this, we have ensured the following: There will only be one ‘resize’ event listener active on the page It will remain active as long as React is rendering our App component All components inside our app (here, just the component) will have access to the WindowDimensionsProvider’s context Content With this plumbing set up, let’s rewrite our responsive layout handling component: import { useWindowDimensions } from '../WindowDimensionsProvider' const ResponsiveLayout = ({ breakpoint, renderMobile, renderDesktop }) => {const { width } = useWindowDimensions()return (width > breakpoint ? renderDesktop() : renderMobile())} Wow! Not only do we have a better performing implementation (as each instance does not have to set up/tear down event listeners), but there’s much less going on in the component. Semantically, each line of code perfectly describes what it is doing: Get the width using window dimensions. If the width is greater than the breakpoint, render the desktop view. Otherwise, render the mobile view. Pretty straightforward. Now that we have this tool, let’s use it to make a responsive layout. Needs More Content So let’s build our two layouts. For our mobile view, we’ll render a tabbed view: React 'react' TabbedView './TabbedView' import from import from MobileView = ({ items }) (<div ='container box'><TabbedView ={items} /></div>) const => className items MobileView export default For the TabbedView, we will use to keep track of the active tab, so it receives proper styles and we know what to render in the content box. useState React, { useState } 'react' import from TabbedView = ({ className, items, renderItem }) { [active, setActive] = useState(0) (<div ='tabs-container'><nav ='tabs is-centered'><ul>{items.map(({ title }, idx) (<li ={idx === active ? 'is-active' : ''} ={title}><a ={() setActive(idx)}>{title}</a></li>))}</ul></nav><div><p ='content'>{items[active].content}</p></div></div>)} const => const return className className => className key onClick => className TabbedView export default Here we simply set up a container for tabs at the top (inside a ) and render the content based on the active tab. nav For Desktops, we will instead give each item its own tile. React 'react' Tile './Tile' { useWindowDimensions } '../WindowDimensionsProvider' import from import from import from DesktopView = ({ items }) { { width } = useWindowDimensions()return (<div ={‘tile is-ancestor ‘.concat(width < 1088 ? ‘is-vertical’ : ‘’)}>{items.map((item, idx) (<Tile ={item.title}{...item}/>))}</div>)} const => const className => key DesktopView export default Our desktop view can even handle width as well, for a minimal change (like adding a class name), without getting too complex. Our Tile is just a couple of divs ‘n’ spans: import React from 'react' const Tile = ({ title, content }) => (<div className='tile is-parent notification box'><div className='tile is-child'><p className='title'>{title}</p><span className='content'>{content}</span></div></div>) export default Tile In , we’ll write a basic wrapper for the responsive layout: src/Content/index.js import React from 'react'import ResponsiveLayout from '../ResponsiveLayout'import MobileView from './MobileView'import DesktopView from './DesktopView' Content = ({ items }) (<ResponsiveLayout ={767} ={() (<DesktopView ={items} />)} ={() (<MobileView ={items} />)}/>) const => breakPoint renderDesktop => items renderMobile => items Content export default Wrapping Up Now we’ve defined our component, we just need some data to render. In , we can create some placeholder data to see how we’re doing so far: Content src/data.json [{ title: "Bananas", "content": "Bananas! I will write some great placeholder text – and nobody writes better placeholder text than me, believe me – and I’ll write it very inexpensively. I will write some great, great text on your website’s Southern border, and I will make Google pay for that text. Mark my words. Lorem Ipsum is a choke artist. It chokes!" },... more data] With everything in place, we should be able to confirm by resizing the window that the view switches from a tabbed view to a set of tiles. I hope this walkthrough has shown a few ways hooks can be used in your React apps today. Here are some of the other great ways I have been able to use hooks in apps recently: Creating an component with OnScrollIntoView IntersectionObserver Creating reusable animation components (using the amazing package) react-spring Building a lazy-loaded Image component for rendering images on slow connections That’s all for this guide! Let me know in the comments if you enjoyed this article, if you would like me to write about something else, or if you have a question.
Share Your Thoughts