Home > Software engineering >  Sidebar Animation using FramerMotion with Chakra UI not working when state is switched to React Cont
Sidebar Animation using FramerMotion with Chakra UI not working when state is switched to React Cont

Time:02-19

I'm trying to create a Sliding Sidebar animation in react with framer-motion using the Grid component of @chakra-ui/react. The animation is working when the state is inside the same component with the animated component.

However, I want to migrate the state to a ContextProvider so that I can toggle the sidebar from anywhere inside the Layout. As soon as I moved the state to Context, the animation is not working anymore. I kind of know the problem is the Context re-rendering it's child. However I don't have any idea how to make the animation works with ContextProvider. Please help me.

In case you want the complete working example, CodesandBox


Working Simple Example (As described from the framer-motion docs)

import { Grid, GridItem, Button } from '@chakra-ui/react'
import { motion, useCycle } from 'framer-motion'

const MotionGrid = motion(Grid)

export function DashboardLayout() {
  const [sidebarState, toggleSidebar] = useCycle('expand', 'collapse')
  const variants = {
    expand: { gridTemplateColumns: '230px 1fr' },
    collapse: { gridTemplateColumns: '55px 1fr' },
  }
  return (
    <MotionGrid
      animate={sidebarState}
      variants={variants}
      gridTemplateRows="80px 1fr"
    >
      <GridItem rowSpan={1} colSpan={2}>
        <Button onClick={() => toggleSidebar()}>ToggleSidebar</Button>
      </GridItem>
      <GridItem>Navbar Here</GridItem>
      <GridItem>Main Content Here</GridItem>
    </MotionGird>
  )
}

Not Working Example when switched to ContextProvider

import * as React from 'react'
import { Grid, GridItem, Button } from '@chakra-ui/react'
import { motion, useCycle } from 'framer-motion'

const SidebarContext = React.useContext({
  sidebarState: 'expand',
  toggleSidebar: () => {},
})
function useSidebar() {
  return React.useContext(SidebarContext)
}
function SidebarProvider({ children }) {
  const [sidebarState, toggleSidebar] = useCycle('expand', 'collapse')
  return (
    <SidebarContext.Provider value={{ sidebarState, toggleSidebar }}>
      {children}
    </SidebarContext.Provider>
  )
}

export function DashboardLayout() {
  const MotionGrid = motion(Grid)
  const { sidebarState, toggleSidebar } = useSidebar()
  const variants = {
    expand: { gridTemplateColumns: '230px 1fr' },
    collapse: { gridTemplateColumns: '55px 1fr' },
  }
  return (
    <MotionGrid
      animate={sidebarState}
      variants={variants}
      gridTemplateRows="80px 1fr"
    >
      <GridItem rowSpan={1} colSpan={2}>
        <Button onClick={() => toggleSidebar()}>ToggleSidebar</Button>
      </GridItem>
      <GridItem>Navbar Here</GridItem>
      <GridItem>Main Content Here</GridItem>
    </MotionGird>
  )
}

// Inside App.js
export default function App() {
  return (
    <SidebarProvider>
      <DashboardLayout></DashboardLayout>
    </SidebarProvider>
  )
}

CodePudding user response:

I got the answer. It is such a common mistake that I make. I was initializing the MotionGrid component inside the Layout component, which will trigger re-render when the state is changed and re-create the new MotionGrid component. Just by re-locating the MotionGrid initializing as a separate component fixed the problem.


Before

export function DashboardLayout() {
  const MotionGrid = motion(Grid)
  ...
}

After

const MotionGrid = motion(Grid)

export function DashboardLayout() {
  return (
    <MotionGrid>
    ...
  )
}
  • Related