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
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>
)
}
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>
)
}
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.
export function DashboardLayout() {
const MotionGrid = motion(Grid)
...
}
const MotionGrid = motion(Grid)
export function DashboardLayout() {
return (
<MotionGrid>
...
)
}