How can I tell if an React-Bootstrap component has finished rendering? - reactjs

I have a React Bootstrap accordion with a lot of data in it that takes a couple seconds to load and doesn't work properly if you try to expand it before that. I would like to hide/overlay the accordion until the render is finished.
To be clear: this is not a question of waiting on the server for the data to load - it's all available on the client, there's just a lot to render. I can't find anything in the React docs about how to handle this case.
I tried using Accordion onLoad event to set a loading flag in component state. However, Bootstrap never seems to actually trigger it and I can't find any other event that I would expect to work.
Update: I am using functional components by the way.

Ok I figured it out, set the loading flag in an empty-array useEffect for the component and it'll trigger when the rendering is complete. Very simple solution, just not as immediately intuitive with functional components.
const [accordionRendering, setAccordionRendering] = React.useState(true);
...
React.useEffect(() => setAccordionRendering(false), [])
...
return <React.Fragment>
<Spinner animation="border" className={accordionRendering ? '' : 'd-none'}/>
<Accordion activeKey={activeKey} onSelect={onAccordionChanged} className={accordionRendering ? 'invisible' : ''}>
...

Related

Modal start and exit animation logic inside a single React component

I have been trying to solve this problem for a very long time. I will be very glad for your help.
I have 3 functional components.
export const HomePage = () => {
const [menu,setMenu]=useState(false)
return (
<>
<Header menuState={()=>setMenu(!menu)} />
{menu&&<Menu/>}
</>
)
}
function Header(props){
return(
<div className='Header'>
<div className='button' onClick={()=>props.menuState()}/>
</div>
)
}
function Menu(props){
function animateExit() {
console.log("I'm trying to call this function from the Header component")
}
useEffect(() => {
function animateStart() {
console.log('Animation works! :)')
}
return()=>{console.log("In this case, the animation will not have time to appear, as the component will be instantly removed")}
},[]);
return(
<div className='Menu'/>
)
}
By clicking the button in the Header component, I am adding a Menu component to the home page. So I open the menu.
For smooth opening, I use an animation function, for example, I'll call it AnimateStart. It's in the "Menu" component in UseEffect .
To exit the menu, press the same button again.
I really want to put AnimateExit in the menu component. So all the logic of a component is inside that component.
For this you need either:
How to call this function from header component
Catch the removal of the component through the return of useEffect, but the function in the return must work until the removal. I don't know if this is possible.
At the moment, I'm writing exit animation logic in the Header component. It is not comfortable. I want to put all menu animation in Menu .
I hope you understand what I mean.
I would be glad for any advice on how to make a self-sufficient component, and not scatter its code into different blocks.
Thank you!
From what I was able to understand, you are currently facing problems with making the exit animation work because the Menu disappears quickly after clicking on the button in the Header component.
That is because your code mentions it clearly that show the Menu component only when menu variable is set to true. Which means your exit animation will not have time to be processed. The way you could handle this is by using CSS classes that have animation effect on them and you can switch between these classes based on the boolean value in your menu variable.
Refer to this example: CSS based animations
Also: Visibility with animation
I'd appreciate it if you could accept the answer if it helps your case!

How to mutate the dom without breaking React

I'm using a library with a component that returns a bunch of HTML. Some of the HTML is ok, and some of it needs changed after rendering.
I'm aware this is unadvised however I am in a situation where this kind of hacky method seems necessary.
My problem is that following the dom mutations, React seems to break. Re-renders no longer occur, and there are no errors in the console to indicate why. This is obviously due to the mutations, but I think there must be some way to do this without breaking React. I've tried various things including using a ref and eg useLayoutEffect.
I have create a minimal-ish repro here:
https://codesandbox.io/s/busy-sea-mz04cr
couple of things here from your sandbox:
Re-renders are still occurring in both the App and MyComponent when you press the buttons, you can prove it by adding a console.log("XXX has re-rendered") in each component
Updating the DOM via a ref will not cause React to re-render
Your custom hook will always only ever execute once (when MyComponent mounts) because the dependencies never change
Your state changes (button presses) are still firing events and updating state but your DOM update via ref removes the rendering of the data variable
Going from
<div id="data">
<p>Data: {data}</p>
</div>
to
<div id="data">
<p>Data: mutation one</p>
</div>
So every button press, your <MyComponent /> is updating and rendering the same static JSX.
Here's a modified sandbox that passes the data state variable to the effect to cause it to trigger on button change + appendChild via ref instead replaceChild so we can see the re-renders
Some things that might be helpful:
Updating the DOM via refs will undoubtedly lead to problems, maybe you should parse the HTML that is returned from your mentioned library and render it with dangerouslySetInnerHTML (assuming it's safe to do so and is from a trusted source otherwise it opens potential exploits)
<div dangerouslySetInnerHTML={{__html: data}} />
Effects (useEffect and useLayoutEffect) are typically reserved for keeping your app in sync with external systems or other non-react effects, you might be able to create an event handler to achieve your goal
function MyComponent() {
const [data, setData] = useState("");
const handleButtonPressed = (html) => {
const fixedHtml = // ... make your changes to the HTML
setData(fixedHtml);
};
return (
<div>
<button onClick={handleButtonPressed}>some button</button>
<p>{data}</p>
</div>
);
}
}
Hope that helps, the new version of the React docs is a fantastic resource.

React/Redux : Detect and exectute instructions before component destruction

Ola,
In my redux state, I have a isEditing boolean value, initiate at false, to manage a profile editing mode. I create a redux action to toggle this state. Below, some screen of the component render.
It works in most cases:
When I click on Edit name, I toggle to true and display the edit form
When I click on Save (or Cancel), it perform (or not) api request, then toggle to false, displaying the initial component
But if I start editing then quit page manualy (via logo link or url), and then come back to this page, obviously, the edit mode is still active at true.
I would like to put my state to false when I leave page, but I don't find any option to dispatch an action before component destroyed (like old componentWillUnmount() class programming method or beforeUnmount VueJS equivalent)
PS : I use react router V6. It seems a hook was implemented during beta but never released, and no news since :(
An alternative is to force value to false on component creation, with useEffect. It works, but I think it's not the right place. I see the edit mode opened then close in my ui during a few ms.
Thanks in advance
Try using the clean up on an useEffect hook
useEffect(() => {
// Specify how to clean up after this effect:
return function cleanup() {
// make redux call to toggle edit mode
};
});
I believe you could dispatch an action in the useEffect return value like useEffect(() => { return () => dispatch(changeEditMode(false)) }, []). It is mostly similar to a componentWillUnmount().

React lazy load expensive material-ui modal

I have got expensive material-ui modal component that takes around 1s to render which I tried to lazy load with Suspense and React.lazy but it didn't work for me, if I understand it correctly it only lazy loads import (?)
About the modal component, there is no fetching from api or anything just many components with hooks and material-ui inside and data passed as props
The process that I have now:
I click button
1s passes
everything is loaded and modal opens
What I'm trying to accomplish"
I click button
Modal immediately pops up and shows some sort of loading screen
When component is ready it shows up
how can I do it in react?
You can apply a simple check in your modal component and render it conditionally. So suppose if your have three data the you can use ternary operator just below the return the data only if all data is loaded and if not then show a loading component
Sample code might make you clear what I'm trying to say. In my case the data was coming from an API so I put a check if that data is loaded or not.
function ModalComponent(data1, data2, data3){
/////// other code //////
return (
<div>
{data1 && data2 && data3 ? (
<div>Your data </div>
) : <div>Loading....</div>
}
</div>)
}
export default ModalComponent

How to re-render a parent from a children component with hook?

I would like to force my parent to re-render the page when I click on a button from a child component.
(I don't use redux because I don't have the time to learn it in my project, so I use localStorage. Unfortunately React don't see when a change is operated on local Storage, so he don't re-render. It's why I would like to force it to re-render my page (to have the right content).)
I tried to use hook with the function useState to do it but it's not working and I don't know why...
(Nothing change in my page)
This is my parent page: (just the code important)
const[reload, setReload] = useState(false);
...
else if (user) { contents = [<Message_UserIdentified user={user} callBack={setReload}/>, contentform]; }
This is my child component:
const Message_UserIdentified = (props) => {
let user = props.user;
return (
<Alert color="primary" className="alert alert-dismissible alert-info">
<h4>Welcome {!user ? "" : user.firstname} {!user ? "" : user.lastname}</h4>
If you are not {!user ? "" : user.firstname} click <a onClick={() => {localStorage.removeItem('idUser'); props.callBack(true);}}>here.</a>
</Alert>
);
}
Why my parent page don't want re-render ?
Thanks in advance.
I have created a proof of concept of what you are trying to achieve and it works:
https://codesandbox.io/s/weathered-smoke-ojr5j
probably there's something else in your code that we can't see that's preventing the component to re render
Your child component can have a prop which directly pass setReload to it.
However one common usage is that, setReload can be associated with an event, ex. onReload. You can pass a prop onReload to the child instead.
<Child onReload={() => { setReload() }} />
Inside onReload implementation, you can call setReload.
The reload state variable in your parent component is strictly local to it; the child can't see it.
I've been using React Hooks for about 2 months now. The learning curve, at times, has been steep but I'm now getting really proficient at it.
A companion technology to Hooks called Context API is perfect for your needs. It's what you should be using rather than local storage because both components can access it. Your child component would set the equivalent of reload in the Context to true and your parent would have a useEffect function that would have reload as a dependency. Thus, when reload is changed from false to true, the useEffect function in the parent would be fired and run the code you desire.
Early on, I very much benefitted from this video series: https://www.youtube.com/watch?v=6RhOzQciVwI&t=46s Watch the first few videos and you should quickly understand how to implement the Context API in your functional React components.

Resources