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>
...
)
}