Is there any way in React to preserve the identity of an element when it moves between different parents?
For example:
const MyComponent = () => {
return (
<div>
{condition() ? (
<Parent1><Child /></Parent1>
) : (
<Parent2><Child /></Parent2>
)}
</div>
);
});
In my case, Child contains a video element, and when the condition() changes mid-way through the video, it gets recreated, which causes hiccups in the playback. I would like it to continue playing smoothly.
I tried:
Memoizing all of the components - didn't help
Using a "key" prop - didn't help either, as the key only matters within the same parent
Passing the Child to the parents as a "children" prop ... didn't help either
There is nothing strange going on in the parents, it can even be something like:
const Parent1OrParent2 = ({children}) => {
return (
<div>
<span>Hello I am parent 1 or parent 2</span>
{children}
</div>
);
});
Is it even possible to preserve the element at all?
the only way to do that is to pre-initialize children so that it is a:
const MyComponent = () => {
const child = <Child />
return (
<div>
{condition() ? (
<Parent1>{child}</Parent1>
) : (
<Parent2>{child}</Parent2>
)}
</div>
);
});
Demo: https://playcode.io/1190983
Related
I have a component which has child components, i want to render these child components with different Ids. They are getting their data from store.The problem is they are rendered but with the same item. how can this be solved?
MultiImages Component
const MultiImages: () => JSX.Element = () => {
const values = ['500', '406', '614'];
return (
<div>
{values.map((val, index) => {
return <OneImage key={index} projectID={val} />;
})}
</div>
);
};
export default MultiImages;
OneImage Component
const OneImage: () => JSX.Element = ({ projectID }) => {
const projectData = useProjectDataStore();
const { getProject } = useAction();
useEffect(() => {
getProject(projectID ?? '');
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
Your issue here - you are calling in a loop, one by one fetch your projects, and each call, as far as we can understand from your example and comments override each other.
Your are doing it implicitly, cause your fetching functionality is inside your Item Component OneImage
In general, the way you are using global state and trying to isolate one from another nodes is nice, you need to think about your selector hook.
I suggest you, to prevent rewriting too many parts of the code, to change a bit your selector "useProjectDataStore" and make it depended on "projectID".
Each load of next project with getProject might store into your global state result, but instead of overriding ALL the state object, you might want to use Map(Dictionary) as a data structure, and write a result there and use projectID as a key.
So, in your code the only place what might be change is OneImage component
const OneImage: () => JSX.Element = ({ projectID }) => {
// making your hook depended on **projectID**
const projectData = useProjectDataStore(projectID);
const { getProject } = useAction();
useEffect(() => {
// No need of usage **projectID** cause it will inherit if from useProjectDataStore
getProject();
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
And inside of your useProjectDataStore store result into a specific key using projectID.
Your component OneImage will return what's in the return statement, in your case:
<>
<div>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
This tag <></> around your element is a React.fragment and has no key. This is the reason you get this error.
Since you already have a div tag wrapping your element you can do this:
<div key={parseInt(projectID)}>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
You can also change the key to Math.floor(Math.random() * 9999).
Note that passing the prop key={index} is unnecessary, and is not advised to use index as keys in a react list.
I want to get a ref, more specifically a getBoundingClientRect() on the <Header/> and <Testimonials/> component. I then want to watch for a scroll event and check if the two components ever overlap. Currently, my overlap variable never flips to true even if what appears on the page is that the two components are overlaping.
const [isIntersecting, setIsIntersecting] = useState(false)
const header = useRef(null)
const testimonials = useRef(null)
const scrollHandler = _ => {
let headerRect = header.current.getBoundingClientRect();
let testiRect = testimonials.current.getBoundingClientRect();
let overlap = !(headerRect.right < testiRect.left ||
headerRect.left > testiRect.right ||
headerRect.bottom < testiRect.top ||
headerRect.top > testiRect.bottom)
console.log(overlap) // never flips to true
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
const App = () => {
return (
<div className="App">
<Header />
<LandingPage />
<div style={{ height: '100vh', backgroundColor: 'black', color: 'white' }}>
</div>
<AboutPage />
<TestimonialsPage />
<Footer />
</div>
);
}
First: Components can't receive directly a ref prop, unless you are wrapping the Component itself in a React.forwardRef wrapper:
const Component = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// Inside your Parent Component:
const ref = useRef();
<Component ref={ref}>Click me!</Component>;
Second: you can also pass a ref down to a child as a standard prop, but you can't call that prop ref since that's a special reserved word just like the key prop:
const Component= (props) => (
<button ref={props.myRef}>
{props.children}
</button>
);
// Inside your Parent Component
const ref = useRef();
<Component myRef={ref}>Click me!</Component>;
This works perfectly fine, and if it's a your personal project you
might work like this with no issues, the only downside is that you
have to use custom prop name for those refs, so the code gets harder to
read and to mantain, especially if it's a shared repo.
Third: Now that you learnt how to gain access to the DOM node of a child Component from its parent, you must know that even if usually it's safe to perform manipulations on those nodes inside a useEffect ( or a componentDidMount ) since they are executed once the DOM has rendered, to be 100% sure you will have access to the right DOM node it's always better using a callback as a ref like this:
const handleRef = (node) => {
if (node) //do something with node
};
<Component ref={handleRef}/>
Basically your function hanldeRef will be called by React during
DOM node render by passing the node itself as its first parameter,
this way you can perform a safe check on the node, and be sure it's
100% valorized when you are going to perform your DOM manipulation.
Concerning your specific question about how to access the getBoundingClientRect of a child Component DOM node, I made a working example with both the approaches:
https://stackblitz.com/edit/react-pqujuz
You'll need to define each of your components as Forwarding Refs, eg
const Header = forwardRef<HTMLElement>((_, ref) => (
<header ref={ref}>
<h1>I am the header</h1>
</header>
));
You can then pass a HTMLElement ref to your components to refer to later
const headerRef = useRef<HTMLElement>(null);
const scrollHandler = () => {
console.log("header position", headerRef.current?.getBoundingClientRect());
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
return (
<Header ref={headerRef} />
);
I'm using TypeScript examples since it's easier to translate back down to JS than it is to go up to TS
I want to make a component but i want that one accepts elements too. For example :
Component :
const Example = () => {
return (
<div>
//Some Elements will come here.
</div>
)
}
Another Page :
const App = () => {
return (
<Example>
<div>
<h1>Hello all </h1>
<p>I want that elements acceptable on my custom component </p>
</div>
</Example>
)
}
But i only can send props and i cant write anything inside of tags of my component. How can i make it ? Thanks for all!
React defined a special prop called children. That's what you exacly need.
Try like this
const Example = ({ children }) => {
return <div>{children}</div>;
};
const App = () => {
return (
<Example>
<div>
<h1>Hello all </h1>
<p>I want that elements acceptable on my custom component </p>
</div>
</Example>
);
};
You can use props.children
const Example = props => {
return <div>{props.children}</div>;
};
I know that there is React portals that may solves the problem, but portals mount child components outside of DOM tree if I understand it correctly. But I need to render child component inside DOM tree but just outside the parent. Here the example.
I have a page:
const Page = () => {
return (
<>
// -> the place to mount <Child_2/> <--
<Parent/>
</>)
I have the Parent:
const Parent = () => {
return (
<>
<Child_1/>
<Child_2/> //<- I need it NOT to mount here but outside the parent
// in the <Page> component and not outside the DOM.
</>
)
How can I do it? And yet can I make it by portal?
In React doc I found an example just for case:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
But it is not my case...
const Page = ()=> {
const [el, setEl] = React.useState(null);
return (
<>
<div ref={setEl}></div>
{el && <Parent el={el} />}
</>
)
}
const Parent = ({el})=> {
React.useEffect();
return (
<>
<Child_1/>
{ReactDOM.createPortal(<Child2 />,el)}
</>
)
}
I have a custom react component as follows
const MyWidget = (props) => (
<div>
{props.options.map((option) => (
<div onClick={(evt) => { onSelect(evt); }}>option</div>
))}
</div>
);
and I want to pass the index of the div to the parent onSelect. The way I have been doing this in the past is:
const MyWidget = (props) => (
<div>
{props.options.map((option) => (
<div onClick={(evt) => { onSelect(option, evt); }}>option</div>
))}
</div>
);
but my widgets would behave more like native widgets if I did
const MyWidget = (props) => (
<div>
{props.options.map((option) => (
<div onClick={(evt) => {
evt.selectedOption = option;
onSelect(evt);
}}>option</div>
))}
</div>
);
so parents can register callbacks in the same way they would if they were using a native element. Are there any reasons why doing this is a bad idea? I guess name clashes would be one, but maybe this is mitigated by the tree structure, where it is the parent's responsibility to know what names are used in child events. Also, are Synthetic Events something that can be relied upon or are they an implementation feature that will change from time to time?