React component switch animation with a loader - Glitchy - reactjs

I have a requirement where i have couple of components out of which only one could be displayed at a particular point of time. The components are of varying heights.
When a user clicks on the button in the currently displayed component i need to
Show a loader to the user
Render the component in background.
Wait till the rendered component calls onLoad callback.
Once onLoad callback is recived, hide the loader.
Something like below (Code sandbox here)
const [loading, setLoading] = useState(false);
const [activeElement, setActiveElement] = useState("Component1");
const onLoad = () => {
setLoading(false);
};
const onClick = () => {
setLoading(true);
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
};
return (
<div className="container">
<ViewWrapperHOC loading={loading}>
{activeElement === "Component1" ? (
<Component1 onLoad={onLoad} onClick={onClick} />
) : (
<Component2 onLoad={onLoad} onClick={onClick} />
)}
</ViewWrapperHOC>
</div>
);
I was planning to write a wrapper component (ViewWrapperHOC in above example) to show the transition between the components and show the loader. Now the problem is when i try to animate, since i have to render both the progressbar and children in viewwrapper, the animation seems to be very glitchy.
Code sandbox here
Could someone suggest a way to fix this. I am open to any alternate animations or any alternate approach to achieve this in any form of pleasant ux. Thanks in advance.

In that second code sandbox, your issue is that as soon as you click, you are changing which element is active, therefore it gets rendered.
You could put a small timeout in the onClick function:
const onClick = () => {
setLoading(true);
setTimeout(() => {
setActiveElement(
activeElement === "Component1" ? "Component2" : "Component1"
);
}, 100);
};
Then in view wrapper you'll need to get rid of transition: "200ms all ease-in-out, 50ms transform ease-in-out".
You need to have a known height here to be able to make a smooth change in height transition.
See codesandbox fork
Version 2:
Here is version 2, using refs. Main changes are moving the box shadow out of the components to the container in view wrapper. Adding refs to the components, passing the active ref to the wrapper and then setting the height with height transition.
To be honest, I'd probably restructure the code a lot if I had the time.

Related

How to useEffect in component that is rendered in another component

This is what I am trying to do:
import { SVG } from '#svgdotjs/svg.js'
const SVGpaper = (props) => {
useEffect(() => {
let canvas = SVG().addTo('#canvas').size(6006, 600);
canvas.rect(100, 100);
});
return (
<div id="canvas">
</div>
)
}
const Profile = () => {
return (
(typeof someVariable !== "undefined") &&
<CCol>
<CRow>
<div>
{SVGpaper()}
</div>
</CRow>
</CCol>
);
};
export default Profile;
Profile is the "big" webpage, it will fetch some data from a server to "someVariable". To make sure it is not rendered prematurely it (Profile) has conditional rendering. When it is eventually rendered I want to create an SVG. I have therefore created a component that creates the SVG and put it into the render of Profile. Because SVG().addTo must be executed after the div id="canvas" actually exist, it is put in a useEffect to prevent it from executing before the div exist. This works great in my head, but not in reality because it breaks the rules of hooks. I get the error:
"Warning: React has detected a change in the order of Hooks called by Profile. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks"
And I should know this, but I am having issues making this code work, so I am trying a little bit of every idea I get, but without any success. How can I change this code and make it render in this order:
conditional rendering for "someVariable"
render the div with id="canvas"
Create the SVG and assign it to the div with id="canvas"
?
Thank you for your help!

React Native - Delay between changing bottom tabs

I have used react navigation's "createBottomTabNavigator" in one of my projects.
const Tab = createBottomTabNavigator();
</Tab.Navigator>
................
<Tab.Screen name="Cart" component={Cart} />
{configuration.paymentProviders.length > 0 && (
<Tab.Screen name="Payment" component={Payment} />
)}
</Tab.Navigator>
My problem is this:
If I switch between two tabs quickly, after a while, the render cannot work properly because it tries to perform the operation without getting a response from the API.
For this, I added setLoading(true) to the API request and did not set it to false until the request was finished, but I still have the same problem, although it is better.
So, I want to give a half-second pause between tabs, do you have any suggestions for this?
When you switch the tabs, you use somthing like onClick() right? Here is old question for the solution as setTimeout. It gives you the ability to delay before the code gets executed. But I advice you do disable all other buttons, before you get a response. This also means that you need to be sure that response will come, at least timeout, then you can enable buttons again.
A better option might be to consider not delaying the process but just simply disabling all buttons which have onClick functionality, to prevent too many requests. This is an example of disabling something, doesn't neccessarely needs to be a button.
export default function App() {
const [clicked, setClicked] = React.useState(false);
//fake request, just delaying things
function handleClick() {
setClicked(true); // disable all buttons if clicked
setTimeout(() => {
setClicked(false);
}, 2000);
}
useEffect(() => {
console.log(clicked);
}, [clicked]);
return (
<div className="App">
<button onClick={!clicked ? handleClick : undefined}>1</button>
<button onClick={!clicked ? handleClick : undefined}>2</button>
</div>
);
}

React/Gatsby: Wrong div loads for a brief moment when conditional rendering

I'm trying to conditionally render a div in Gatsby in an effort to build a responsive nav menu. Unfortunately, I'm getting a quick flash of the menu div just before the full navigation menu loads. Any tips or tricks to resolve this would be appreciated!
import React, { useEffect, useState } from "react"
import * as navlinksStyles from "./navlinks.module.scss"
const NavLinks = () => {
const [windowDimension, setWindowDimension] = useState(null)
useEffect(() => {
setWindowDimension(window.innerWidth)
}, [])
useEffect(() => {
function handleResize() {
setWindowDimension(window.innerWidth)
}
window.addEventListener("resize", handleResize)
return () => window.removeEventListener("resize", handleResize)
}, [])
const isMobile = windowDimension <= 740
return (
<div className={navlinksStyles.wrapper}>
{isMobile ? (
<div>
<h1 className={navlinksStyles.menu}>menu</h1>
</div>
) : (
<div className={navlinksStyles.navlinksWrapper}>
<ul>
<li>About</li>
<li>Services</li>
<li>Frames</li>
<li>Lenses</li>
<li>Locations</li>
<li>Mailbox</li>
</ul>
</div>
)}
</div>
)
}
export default NavLinks
Gatsby does server-side rendering, which involves rendering your React components in a Node environment and saving out the markup produced as a static file. When someone visits one of your pages in production (or in development if you're using SSR in dev), React renders your components and associates them with the already-visible DOM nodes in a process referred to as “rehydration”.
This is all important to know because useEffect (or class-based API methods like componentDidMount) don't run during SSR. They only run once the code is rehydrated client-side. Further, if the DOM nodes produced server-side don't match up with what React renders client-side on the initial render (before any useEffect hooks run), you wind up with a hydration mismatch error that prompts React to throw away the DOM nodes that exist and replace them with what it has produced client-side.
Armed with this info, you can start to debug what might be happening to cause a flash of unexpected content and how to address it:
Server-side, windowDimension is null, and null <= 740 is true, so isMobile is set to true
The output produced server-side then shows the div>h1 element that you're expecting mobile visitors to see
Client side, React rehydrates and fires useEffect hooks, the first of which calls a state setter passing a number greater than or equal to (presumably) 740, prompting a re-render
The component re-renders with the updated value and isMobile is set to false, updating the output to the full nav menu
Once approach to solving this is to wait to render markup or render a placeholder until you’re rendering in a browser:
// Note: do NOT do this!
const NavLinks = () => {
if (typeof window === "undefined") return null
return (
<div>Your content</div>
)
}
The problem with this, as alluded to above, is that you wind up producing different markup server-side than you do in a browser, causing React to throw away DOM nodes and replace them. Instead, you can ensure the initial render matches the server-side output by leveraging useEffect like so:
const NavLinks = () => {
const [ready, setReady] = useState(null)
useEffect(() => { setReady(true) }, [])
// note: the return value of an `&&` expression is the value of the first
// falsey condition, or the last condition if all are truthy, so if `ready`
// has not been updated, this evaluates to `return null`, and otherwise, to
// return <div>Your content</div>.
return ready && <div>Your content</div>
}
There is another approach that works in many scenarios that will avoid the extra render, DOM update/layout/paint cycle: use CSS to hide one of the DOM branches as relevant:
/** #jsx jsx */
import { jsx } from "#emotion/core"
const mobile = "#media (max-width: 740px)"
const NavLinks = () => (
<div>
<div css={{ display: "none", [mobile]: { display: "block" } }}>
Mobile menu
</div>
<div css={{ [mobile]: { display: "none" } }}>
Desktop menu
</div>
</div>
)
I prefer this approach when possible as it cuts down on layout thrashing on-load, but if the DOM trees are large, the extra markup can slow things down as well. As with anything, do some testing for your own use cases and select what works best for you and your team.
Your page is rendered with windowDimension as null, so when someone with a wider windowDimension visit your page, they'll see a brief flash of mobile layout before React kicks in & render the correct one.
You can get around this by using #media query instead.

Prevent event from propagating to children

I am building a React component to be wrapped around other components that will grey-out the children and (hopefully) make them unreactive to clicks.
My approach is:
const TierGater: React.FC = (props) => {
const handleTierGaterClick = (e: React.MouseEvent<HTMLDivElement>) => {
console.log("outer - should be called");
// e.stopPropagation();
// e.preventDefault();
};
return (
<div onClick={handleTierGaterClick}>
<div
onClick={() => {
console.log("inner - should not be called");
}}
>
{props.children}
</div>
</div>
);
};
export default TierGater;
I would like this to be a generic solution that allows me to wrap components arbitrarily without needing to modify the onClick of children components.
I have scanned various solutions but my understanding is that event handlers will fire from children to parents order. This is corroborated by console logs for the above example (inner called first).
I believe I can change the children to inspect the event target and abort onClick logic based on that, but this defeats the purpose of the component - it should wrap around any children without modifying the children.
Is the above possible? If yes, what changes would need to be introduced?
Side question: my event handler is typed with React.MouseEvent<HTMLDivElement> - how can I make this work with both mouse and touch events?
With onClickCapture, you can intercept the events in the capturing phase, before they've made it down to the children, and stop them from propagating:
<div onClickCapture={handleTierGaterClick}>
const handleTierGaterClick = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
e.preventDefault();
};
To be more UI-friendly, a faded background and perhaps pointer-events: none would be good too.

IconButton works on secound click

It's driving me nut's. onClick handler is firing on secound click. I dont know why. The functional component is so simple that should work. This component is a part of another comp. so it's a child. Parent comp is on the list. That is whole setup. If I click first time I saw on the debug that it's not's going fire the event. Style span of IconButton are changing in first click. Strange
const MoreInfo = () => {
const [more_info, setMore_info] = useState("test");
const handleExpand = event => {
if (icon_expand === "expand_more") {
setMore_info("test2");
}
};
return (
<IconButton onClick={handleExpand} aria-label="location" size="small">
<Icon style={{ fontSize: 12 }}>{icon_expand} </Icon>
</IconButton>
);
};
export default MoreInfo;
Actually, it does work as you expected.
I just copy pasted your code in codesandbox and it does work here.
https://codesandbox.io/s/zen-dan-d5r8r
However, as you mentioned the whole tree of components and it's child component of any parent component.
So I guess and you should check some of this:
check onClick of parent and make sure it's not conflicting with any other onClick ( name ).
make sure state updation works properly
check all methods are binded ( only if applicable )

Resources