use react component instead of motion's elements in Framer motion - reactjs

Hi I have a component like this :
const Comp = (props) => {
return(
<div>
data here
</div>
)
}
and I want to use framer motion like this:
const Container = () => {
return(
<motion.Comp variants={variants} someProp="someProp"/>
)
}
The reason I want to do this is that I don't think making a lot of div as a wrapper is a great idea.
I could use motion.div in "Comp" component but some of my app component is made that I don't need animating all of them in my whole app. I just want a solution to add animations to the first element in the component.
Also I searched and I found a solution "motion(Comp)", but it's not working.

From the docs (https://www.framer.com/docs/component/#custom-components), you can wrap any component with motion() to use it as if it were a framer-motion component. You mentioned that you found that solution but that it's not working, and without any more details about it I can't give you a specific reason why. But most likely you're forgetting to pass the ref through to your component, like this:
const Foo = React.forwardRef((props, ref) => <div ref={ref} />)
The ref is the way that framer-motion will identify which element on the page is the one it should be animating, so it needs to be passed to the correct element. Once that's done, you can wrap it with motion:
const MotionFoo = motion(Foo)
Keep in mind that regardless of how many elements your custom component has, only the one that has the ref passed to it will be animated. For example:
const BusyFoo = React.forwardRef((props, ref) => (
<div>
<div ref={ref}>I will be animated!</div>
<div>I won't be animated. :(</div>
</div>
)

Related

Why do (some) CSS styles break when I defined a React functional component inside another functional component?

Why does defining a React functional component inside another functional component break CSS transitions?
function Doohick({isOpen}: {isOpen: boolean}) {
const style = {
transition: 'opacity 2s ease',
...(isOpen ? {opacity: 1} : {opacity: 0})
}
return (
<div style={style}>
Doohick!!!
</div>
)
}
function Parent() {
const [open, isOpen] = useState(false)
return (
<>
<button onClick={() => setIsOpen(!isOpen)}>Toggle Doohick</button>
<Doohick isOpen={isOpen} />
</>
)
}
If I define Doohick outside of Parent, as above, everything works great. If I move the definition inside Parent, with no other changes, my CSS transitions break. Other CSS properties are fine.
Why does defining a functional component inside another functional component break CSS transitions?
Complicated Explanation of Why I Want To Do This
I hear you asking: why would I want to do that? I'll tell you, but bear in mind you don't need to know any of this to understand the specific problem.
I want to encapsulate the Doohick state in a custom hook:
function useDoohick() {
const [isOpen, setIsOpen] = useState(false)
const ToggleButton =
<Button onClick={() => setIsOpen(!isOpen)}>Toggle Doohick</Button>
const Doohick = <MyDoohick show={isOpen}/>
return {ToggleButton, Doohick}
}
function Parent() {
const {Doohick, ToggleButton} = useDoohick()
return (
<>
{ToggleButton}
{Doohick}
</>
)
}
But I also want the Parent to be able to pass its own props into Doohick or ToggleButton. I can almost achieve that that like this:
function useDoohick() {
const [isOpen, setIsOpen] = useState(false)
const ToggleButton = ({text}) =>
<Button
onClick={() => setIsOpen(!isOpen)}
>
{text}
</Button>
const Doohick = () =>
<MyDoohick show={isOpen} />
return {ToggleButton, Doohick}
}
function Parent() {
return (
<>
<ToggleButton text='Burninate' />
<Doohick />
</>
)
}
This works as advertised: ToggleButton renders with the expected label and controls whether or not Doohick is shown. But this pattern breaks some CSS styles (specifically, transitions) I have defined on Doohick. Other styles are fine.
I can still call it like this:
function Parent() {
return (
<>
{ToggleButton({text: 'Burninate'})}
{Doohick()}
</>
)
}
...and the transitions work correctly. But I would much prefer the standard JSX syntax here:
<ToggleButton text='Burninate />
Clearly, <Doohick /> and Doohick() are different. But what is it about the former that breaks CSS transitions here?
The root of the problem boils down to defining the custom components inside the Parent. The hook itself is irrelevant. But this pattern of encapsulating state in a custom hook while returning a customizable component is really powerful and almost works, so I'm hoping there's a way it can be saved.
TL;DR
Why does defining a component within another component break my CSS transitions (and possibly other styles I haven't found yet)? How can I get around this while still calling my nested component with JSX-style syntax?
Defining a component inside another component will always result in issues like this. Every time the outer component renders, you create a brand new definition of the inner component. It may have the same text as the one from the previous render, but it's a different function in memory, so as far as react can tell it's a different type of component.
The component type is the main thing that react looks for when reconciling changes. Since the type changed, react is forced to unmount the old component and then mount the new one. So rather than having a <div> on the page who's style is changing, you have a div with some style, then it gets deleted and an unrelated div gets put onto the page. It may have a different style, but since this is a brand new div, the transition property won't do anything.

React: wrap child component in div if it does not return null

Using React, how can a child component be wrapped conditionally in another element if it is not returning null when rendered?
export const Parent = ({children}) => (
<div className="row">
{React.Children.map(children, child =>
<div className="col-6 col-s-4 col-md-3">
{child}
</div>
)}
</div>
);
This might be related to the discussions in React component children detect if empty / null before render however, none of the proposed approached seemed to work (ReactDOMServer.renderToStaticMarkup seem to have issues with SSR and React.isValidElement is persistently treating the component that returns null as a valid element).
I've got a feeling that this is sort of an anti-pattern, as it seems to be real hard to do. Only solution I can think of at the moment is moving the column div into the child component and clone a prop into the child to inform it that a wrapping column div is desired... Which seems way hacky.
If you don't want to use
const isChildNull = children => {
return !Boolean(ReactDOMServer.renderToStaticMarkup(children));
};
try with:
const isChildNull = children => {
return Boolean(children.type() === null); //--> will return the tag type
};
If child component receives props, don't forget to pass those props when calling type function:
const isChildNull = children => {
const {type, props} = children;
return Boolean(type(props)=== null);
};
EDIT: working example here: https://stackblitz.com/edit/react-a29daw

React renders full component instead of what changed when using wrapper function

I observed below when rendering a list component. The list component is taking a search prop and filtering before rendering.
<UserList pattern={search}/>
when using above statement directly inside parent component, it renders only what changed based on search prop (list remains and only unmatched items got removed)
But when I wrap the UserList in another function like below:
const Users = ()=> <UserList pattern={search}/>;
and use <Users/> in parent component, I see that whole list component re-renders when search prop changes. search field is a local state (useState['']).
so, the parent component looks like this now :
const App = () => {
const [search, setSearch] = useState('');
const Users = ()=> <UserList pattern={search}/>;
return (
<div id="app" className="home-page">
<Header />
<WelcomeSlogan/>
<Users/>
</div>
);
}
So we know that react re renders full child component(s) if parent component changed, but in this case how the parent (<Users>) is changing exactly? Can someone explain to help me understand better? Thanks.

How to let react only render part of the component?

I am writing a react app with css grids. I'm not going to include the css here but it is a 2x2 grid.
import { useState } from 'react';
function Container() {
const [count, setCount] = useState(0);
return (
<div className = "gridwrapper">
<div className = "top_left"> <SomeCustomComponent></div>
<div className = "bottom_left"> <CustomCounter counter = {count}></div>
<div className = "bottom_right"> <CustomCounter counter = {count+2}></div>
<div className = "top_right"><button onClick={() => setCount(count + 1)}>Click me</button><div>
</div>
);
}
function CustomCounter({count}){
return(<p>The count is {count}</p>)
}
I have two issues right now
Since setState would cause re-render, now it would re-render the whole thing. But I only need the bottom two cells to re-render since other parts of my Container component do not even depend on props.
In order for my grid structure to work properly, I need to wrap them in divs, why is that? I tried to assign classnames directly before but it didn't work
Regarding unnecessary re-renders - don't worry about it. The React diffing engine is smart enough to know which elements have actually changed and which ones haven't, and it will only update what is necessary.
You could use a useEffect listener to only change when specific variables change. As far as I know you can not just re-render a part of the component.
When you make a custom component, you need to make the className prop available on the child component aswell.
e.g.
function SomeCustomComponent(props) {
return (
<div className={props.className}>
// ... your component stuff here ..
</div>
)
}

Improve the performance of Reactjs for rendering large amount of components

I am developing a gallery and I have a performance problem when I exceed 200 components.
Currently in my gallery I can filter images and sort them according to certain attributes.
I tried to use the "Lazy Load" to improve the performance but it brings me other problems that I did not have before. Moreover it does not solve the problem if I still display all my elements.
I would like to try a thing a bit like "react-virtualized" which updates the DOM permanently but I do not really know how to do it and I have the impression that "react-virtualized" was not thought for operate with entire components.
My Gallery component looks like that:
const Gallery = memo(props => {
const { imgs } = props;
const { onClickHere } = props;
return (
<div className="cards-container">
{imgs.map((img, index) => (
<GalleryImage
img={img}
index={index}
onClickHere={onClickHere}
/>
))}
</div>
);
});
My Image component looks like that:
const Image = props => {
const { img } = props;
const { index } = props;
const { onClickHere } = props;
const { alt } = props;
return (
<div className="cards-child">
<div className="gallery-card">
<img className="gallery-thumbnail" src={img.uri} alt={alt} />
<div className="card-info">
<span className="card-info_text">
<span className="badge badge-secondary">
{img.attr.join(", )}
</span>
</span>
</div>
<span className="card-icon-open fa fa-expand" onClick={e => {onClickHere(e, img, index);}} />
</div>
</div>
);
};
These components display a clickable image list that is all the same size.
Do you know if I can actually use react-virtualized and did it badly (if that's the case can you help me to see more clearly) or do you know a way to improve these performances ?
On one hand we have the render cycle from the Virtual DOM. In this case React will render every component when its props change or when its parent renders.
To improve performance here you could memoize the component so that it will only render if its props change, and not when the parent changes. So in your case, you probably want to memoize Image rather than Gallery. Note that Gallery is receiving onClickHere and passing it to Image. Make sure the callback is wrapped in a useCallback so a new reference is not created on every render (which memo would recognize as a new parameter and render the component).
On the other hand we have the actual DOM rendering. In this case React will only render anything if there is a change, and only change the necessary HTML.
Virtualization plugins help to avoid rendering all the HTML, and only redering the necessary. This is helpful so the browser has the minimum necessary HTML, because if the amount of nodes is large it might be laggy. Before implementing these try to reduce the amount of divs inside Image to a minimum.
Only apply performance improvements when necessary and test to see if you are getting benefits from them or not. In your case the main issue might be displaying too many HTML elements. Also it seems like your map function is missing a key.

Resources