{"id":1,"date":"2026-03-31T03:21:41","date_gmt":"2026-03-31T03:21:41","guid":{"rendered":"https:\/\/vanianettleford.com\/?p=1"},"modified":"2026-03-31T22:52:00","modified_gmt":"2026-03-31T22:52:00","slug":"hello-world","status":"publish","type":"post","link":"https:\/\/vanianettleford.com\/?p=1","title":{"rendered":"Avoiding JS Code Smells for Layout and Transitions"},"content":{"rendered":"\n<p>In real-world React applications, components often start small but quickly grow into complex beasts that are hard to maintain. One pattern I noticed repeatedly is using <code>useEffect<\/code> for tasks that could either live elsewhere or be handled more elegantly with CSS.<\/p>\n\n\n\n<p>In this post, I\u2019m walking through a refactor of a video carousel component, highlighting:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>How to move side-effect logic into reusable hooks<\/li>\n\n\n\n<li>How to reduce code smells like detecting mobile via JavaScript<\/li>\n\n\n\n<li>Lessons for making your components cleaner, testable, and maintainable<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>The Problem<\/strong><\/p>\n\n\n\n<p>Imagine a carousel component that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Handles multiple slides, including videos<\/li>\n\n\n\n<li>Tracks which slide is active<\/li>\n\n\n\n<li>Detects viewport size to adjust layout<\/li>\n\n\n\n<li>Manages UI transitions<\/li>\n<\/ul>\n\n\n\n<p>In many implementations, developers will handle transitions and viewport detection directly in the component using <code>useEffect<\/code>. While functional, this pattern creates long, cluttered components that are harder to test and maintain.<\/p>\n\n\n\n<p>Here\u2019s a simplified example of the \u201csmelly\u201d part of the component:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>useEffect(() => {\n  setIsMobile(window.innerWidth &lt; 768);\n  const handleResize = () => setIsMobile(window.innerWidth &lt; 768);\n  window.addEventListener('resize', handleResize);\n  return () => window.removeEventListener('resize', handleResize);\n}, &#91;]);<\/code><\/pre>\n\n\n\n<p><strong>Why it\u2019s a smell:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Adds extra state and triggers re-renders on every resize<\/li>\n\n\n\n<li>Harder to maintain than CSS media queries<\/li>\n\n\n\n<li>Blurs the line between layout (CSS) and behavior (JS)<\/li>\n\n\n\n<li>Makes testing tricky<\/li>\n<\/ol>\n\n\n\n<p><strong>The Refactor: Using Hooks<\/strong><\/p>\n\n\n\n<p>To clean up the component, I moved all side-effect logic into custom hooks, leaving the component itself focused purely on rendering and interaction.<\/p>\n\n\n\n<p><strong>1. useTransition Hook<\/strong><\/p>\n\n\n\n<p>Handles slide transitions and timing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { useState, useEffect } from 'react';\n\nexport function useTransition(duration: number = 500) {\n  const &#91;isTransitioning, setIsTransitioning] = useState(false);\n\n  useEffect(() => {\n    if (!isTransitioning) return;\n    const timeout = setTimeout(() => setIsTransitioning(false), duration);\n    return () => clearTimeout(timeout);\n  }, &#91;isTransitioning, duration]);\n\n  const startTransition = () => setIsTransitioning(true);\n\n  return { isTransitioning, startTransition };\n}\n<\/code><\/pre>\n\n\n\n<p><strong>Benefits:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Keeps transition logic reusable, testable, and isolated<\/li>\n\n\n\n<li>Hooks can be unit-tested independently<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>2. useViewport Hook<\/strong><\/p>\n\n\n\n<p>Detects viewport size only if needed, but in many cases, CSS would be enough:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import { useState, useEffect } from 'react';\n\nexport function useViewport(breakpoint: number = 768) {\n  const &#91;isMobile, setIsMobile] = useState(false);\n  const &#91;hasLoaded, setHasLoaded] = useState(false);\n\n  useEffect(() => {\n    const checkViewport = () => setIsMobile(window.innerWidth &lt; breakpoint);\n\n    checkViewport();\n    setHasLoaded(true);\n\n    window.addEventListener('resize', checkViewport);\n    return () => window.removeEventListener('resize', checkViewport);\n  }, &#91;breakpoint]);\n\n  return { isMobile, hasLoaded };\n}<\/code><\/pre>\n\n\n\n<p><strong>Smell discussion:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>If the only reason you\u2019re using JS here is for styling\/layout, CSS media queries are simpler and more maintainable:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>.carousel {\n  width: 900px;\n}\n\n@media (max-width: 768px) {\n  .carousel {\n    width: 300px;\n  }\n}<\/code><\/pre>\n\n\n\n<p>JS-based detection is only justified if you need conditional rendering or behavior that CSS can\u2019t handle.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>The Clean Component<\/strong><\/p>\n\n\n\n<p>Here\u2019s the refactored carousel using the hooks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export function VideoCarouselSection({ title, subtitle, slides = &#91;], enableInlineVideo }: Props) {\n  const isEven = slides.length % 2 === 0;\n  const &#91;currentIndex, setCurrentIndex] = useState(Math.floor(slides.length \/ 2));\n  const &#91;hasInteracted, setHasInteracted] = useState(false);\n\n  const { isTransitioning, startTransition } = useTransition();\n  const { isMobile, hasLoaded } = useViewport();\n\n  const handleNext = () => {\n    if (isTransitioning) return;\n    startTransition();\n    setCurrentIndex(prev => prev > slides.length - 2 ? (isEven ? 1 : 0) : prev + 1);\n  };\n\n  const handlePrev = () => {\n    if (isTransitioning) return;\n    startTransition();\n    setCurrentIndex(prev => prev &lt; (isEven ? 2 : 1) ? slides.length - 1 : prev - 1);\n  };\n\n  return (\n    &lt;section>\n      {title &amp;&amp; &lt;h1>{title}&lt;\/h1>}\n      {subtitle &amp;&amp; &lt;p>{subtitle}&lt;\/p>}\n\n      &lt;div className=\"carousel-wrapper\">\n        &lt;Carousel\n          active={currentIndex}\n          next={handleNext}\n          prev={handlePrev}\n          config={{\n            ...(hasLoaded ? { width: isMobile ? 300 : 150 } : {}),\n            maxSlideWidth: 900,\n          }}\n          slides={slides.map(slide => (\n            &lt;Slide\n              key={slide.id}\n              slide={slide}\n              enableInlineVideo={enableInlineVideo}\n              nextSlide={handleNext}\n              hasInteracted={hasInteracted}\n              setHasInteracted={setHasInteracted}\n            \/>\n          ))}\n        \/>\n      &lt;\/div>\n    &lt;\/section>\n  );\n}<\/code><\/pre>\n\n\n\n<p><strong>Key benefits of this refactor:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Separation of concerns \u2013 hooks handle side effects; component handles rendering<\/li>\n\n\n\n<li>Reusability \u2013 hooks can be used anywhere you need transitions or viewport awareness<\/li>\n\n\n\n<li>Testability \u2013 hooks can be unit-tested independently<\/li>\n\n\n\n<li>Cleaner layout \u2013 avoids unnecessary JS for things CSS can handle<\/li>\n<\/ol>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Cleaner layout \u2013 avoids unnecessary JS for things CSS can handle<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Takeaways for Engineers<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Refactor side effects into hooks \u2013 keeps components lean and easier to reason about<\/li>\n\n\n\n<li>Avoid JS for styling\/layout if CSS media queries can do the job<\/li>\n\n\n\n<li>Always clean up effects \u2013 event listeners, timers, and subscriptions can leak memory<\/li>\n\n\n\n<li>Focus on maintainability \u2013 even small refactors have big payoffs over time<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>In real-world React applications, components often start small but quickly grow into complex beasts that&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1","post","type-post","status-publish","format-standard","hentry","category-refactor"],"_links":{"self":[{"href":"https:\/\/vanianettleford.com\/index.php?rest_route=\/wp\/v2\/posts\/1","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vanianettleford.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vanianettleford.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vanianettleford.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vanianettleford.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1"}],"version-history":[{"count":3,"href":"https:\/\/vanianettleford.com\/index.php?rest_route=\/wp\/v2\/posts\/1\/revisions"}],"predecessor-version":[{"id":22,"href":"https:\/\/vanianettleford.com\/index.php?rest_route=\/wp\/v2\/posts\/1\/revisions\/22"}],"wp:attachment":[{"href":"https:\/\/vanianettleford.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vanianettleford.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vanianettleford.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}