Unmounting ReactJS element - reactjs

I have created an element with ReactJS and appended it to div container and then removed the same element using jQuery.remove().
Do I need to unmount the node that I created with React?
Any help will be appreciated!

Yes, you should do so. You can remove the element by
React.unmountComponentAtNode(document.getElementById('divsId'));
where the div you are removing the element from has to be the one you were originaly appending it to.

Interfering with the DOM causes unnecessary repainting and redrawing which are expensive processes. The best method is to set a flag like so:
getInitialState() {
isVisible: false
}
toggleVisiblity() {
var {isVisible} = this.state
this.setState({isVisible: !isVisible})
}
render() {
if(isVisible)
var display = <h2> Your content </h2>
return(
<button onClick={this.toggleVisibility}> Click here </button>
{display}
)
}
When isVisible is false, React automatically unmounts and when it is true, it mounts. This is a good practice to maybe even add animations.

Related

React - How to notify popper to reposition my popover whenever react updates any DOM element

Relevant versions: React 16.4.2, Bootstrap 4.1.3, popper.js 1.14.4, Typescript 3.0.3
I use the Bootstrap Popover functionality in my react app.
The Popover works well if the rest of the page is static. When the page is changed (at the browser level), the Popover gets repositioned very quickly and smoothly so it stays visible while the content it's anchored to is visible:
when scrolling if it bumps up against the windows edges
if the screen is rotated on a phone
if the window is resized
This all works well because popper.js is apparently watching the window.scroll and window.resize events, as per this answer: Bootstrap 4 - how does automatic Popover re-positioning work?
The problem comes when my react application starts showing/hiding DOM elements. Because popper.js doesn't know about react, it doesn't know the DOM changed, so it doesn't know that the Popovers might need to be repositioned.
I know calling popover("update") on each Popover anchor works, because I've added code like this to do it intermittently:
window.setInterval(()=> $(this.selfRef).popover("update"), 100);
But that's yucky and wasteful, and a little janky.
Is there a way to have react tell me when it updates any node in the DOM, so I can then tell popper.js to update the position of the popovers?
Note that the react component that causes the DOM change isn't necessarily located near the component that uses the Popover. It could be something in a completely separate part of the hierarchy that happens to be displayed before the component with the popover - so the I don't think the solution is componentWillReceiveProps() or methods like that on the Popover component, because it's probably not the component that's causing the movement.
Note that I'm aware of projects like react-bootstrap, reactstrap or react-popper - but I don't want to use them.
EDIT: it seems like MutationObserver might be a non-react way to do this. I just figured since React is already doing all that reconciliation work, maybe there's a way to get it to notify me when it actually does edit the DOM.
"The react Component that causes the DOM change isn't necessarily
located near the Component that uses the Popover. It could be
something in a completely separate part of the hierarchy"
If both the Component that changes the DOM, and the Component that creates the Popover are in the same parent, you could share a method in the parent that does the .popover('update'). The Component that changes the DOM would need to trigger this event, but it doesn't need to be specifically "aware" of the Popover Component. The Popover Component doesn't need to be aware of the DOM changing Component.
class ChangeDom extends React.Component {
constructor(props) {
super(props);
this.changeDom = this.changeDom.bind(this);
}
changeDom () {
this.props.domChanged();
}
render() {
return (
<div>
<button className="ml-2 btn btn-primary" onClick={this.changeDom}>Change Dom
</button>
</div>)
}
}
class Pop extends React.Component {
constructor(props) {
super(props);
this.togglePopover = this.togglePopover.bind(this);
}
togglePopover() {
$('[data-toggle="popover"]').popover('toggle');
}
render() {
return (
<div class="position-relative">
<button className="mt-4 btn btn-primary" onClick={this.togglePopover} data-toggle="popover"
</button>
</div>)
}
}
class Parent extends React.Component {
domChanged(){
$('[data-toggle="popover"]').popover("update");
}
render() {
return (
<div>
<ChangeDom domChanged={this.domChanged} />
<Pop />
</div>)
}
}
Demo: https://www.codeply.com/go/NhcfE8eAEY
This is my current attempt at a MutationObserver based solution.
UserApp is a component placed toward the top of the application hierarchy.
The Popover class is (over) used in various places in my application for a bunch of stuff.
The possibility of infinite recursion caused by firing popover("update") from a MutationObserver event makes me wary of using this solution long term.
It seems to do the job for now, but this is one of the things uni-directional binding is meant to avoid.
On the plus side, this works even when you have non-react components in your application (like for example, the Bootstrap navbar).
export class UserApp extends React.Component<any, AppState> {
public domChangeObservers = $.Callbacks();
public mutationObserver = new MutationObserver(
(mutations: MutationRecord[])=>{
// premature optimisation?
// I figure I don't care about each individual change, if the browser
// batched em up, just fire on the last one.
// But is this a good idea given we have to inspect the mutation in order
// to avoid recursive loops?
this.domChangeObservers.fire(mutations[mutations.length-1]);
}
);
constructor(props: any) {
super(props);
this.mutationObserver.observe(document.documentElement, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
});
}
componentWillUnmount(){
this.mutationObserver.disconnect();
}
...
}
const DefaultTrigger = "click";
export interface PopoverProps{
popoverTitle: string | Element | Function;
popoverContent: string | Element | Function;
/** Set to "focus" to get "dismiss on next click anywhere" behaviour */
popoverTrigger?: string;
/** Leaving it empty means that the popover gets created
* as a child of the anchor (whatever you use as the child of the popover).
* Setting this to "body" means the popover gets created out on the body
* of the document.
* "body" can help with stuff like when the popover ends up
* being clipped or "under" other components (because of stuff like
* `overflow:hidden`).
*/
container?: string;
allowDefaultClickHandling?: boolean;
ignoreDomChanges?: boolean;
id?: string;
}
export class Popover
extends PureComponent<PopoverProps, object> {
// ! to hack around TS 2.6 "strictPropertyInitialization"
// figure out the right way... one day
selfRef!: HTMLSpanElement;
onDomChange = (mutation:MutationRecord)=>{
/*
- popover("update") causes DOM changes which fire this handler again,
so we need to guard against infinite recursion of DOM change events.
- popover("update") is async, so we can't just use an "if not currently
handling a mutation" flag, because the order of events ends up being:
onDomChange() -> flag=true -> popover("update") -> flag=false ->
popper.js changes DOM -> onDomChange() called again -> repeat forever
- Can't just detect *this* popover. If DOM event occurs because popovers
overlay each other they will recurse alternately - i.e. pop1 update
call makes DOM changes for pop2, pop2 update makes changes for pop1,
repeat forever.
*/
if( Popover.isPopoverNode(mutation) ){
return;
}
/*
- tell popper.js to reposition the popover
- probably not necessary if popover is not showing, but I duuno how to tell
*/
$(this.selfRef).popover("update");
};
private static isPopoverNode(mutation: MutationRecord){
/*
Had a good attempt that used the structure of the mutation target to
see if it's parent element was defined as `data-toggle="popover"`; but
that fails when you set the `container` prop to some other element -
especially, "body", see the comments on the Props .
*/
if( mutation.target.nodeType != 1 ){
return false;
}
// Is Element
let element = mutation.target as Element;
/*
Is the mutation target a popover element?
As defined by its use of the Bootstrap "popover" class.
This is dodgy, it relies on Bootstrap always creating a container
element that has the "popover" class assigned.
BS could change their classname, or they could
change how they structure their popover, or some other
random widget could use the name.
Actually, this can be controlled by overriding the popover template,
which I will do... later.
*/
let isPopoverNode = element.classList.contains("popover");
// very helpful when debugging - easy to tell if recursion is happening
// by looking at the log
// console.log("target", isPopoverNode, mutation, mutation.target );
return isPopoverNode;
}
componentDidMount(): void{
// the popover() method is a "JQuery plugin" thing,
// that's how Bootstrap does its stuff
$(this.selfRef).popover({
container: this.props.container || this.selfRef,
placement: "auto",
title: this.props.popoverTitle,
content: this.props.popoverContent,
trigger: this.props.popoverTrigger || DefaultTrigger,
});
if( !this.props.ignoreDomChanges ){
UserApp.instance.domChangeObservers.add(this.onDomChange);
}
}
componentWillUnmount(): void {
if( !this.props.ignoreDomChanges ){
UserApp.instance.domChangeObservers.remove(this.onDomChange);
}
// - without this, if this component or any parent is unmounted,
// popper.js doesn't know that and the popover content just becomes
// orphaned
$(this.selfRef).popover("dispose");
}
stopClick = (e: SyntheticEvent<any>) =>{
if( !this.props.allowDefaultClickHandling ){
// without this, if the child element is an <a> or similar, clicking it
// to show/dismiss the popup will scroll the content
e.preventDefault();
e.stopPropagation();
}
};
render(){
let popoverTrigger = this.props.popoverTrigger || DefaultTrigger;
// tabIndex is necessary when using "trigger=focus" to get
// "dismiss on next click" behaviour.
let tabIndex = popoverTrigger.indexOf("focus")>=0?0:undefined;
return <span id={this.props.id}
tabIndex={tabIndex}
ref={(ref)=>{if(ref) this.selfRef = ref}}
data-toggle="popover"
onClick={this.stopClick}
>{this.props.children}</span>;
}
}

Does React need to Re-Render Textbox at its OnChange Event

I've been working on a React project recently. I've gotten to the point where I know that when typing in a textbox React updates its UI state and re-renders the textbox and brings focus back to it. The whole process happens so fast that user doesn't notice that textbox has been re-rendered while typing.
Now here my question is that why doesn't React just update UI state of textbox at onChange event and doesn't re-render the textbox and hence doesn't require to put focus on it.
Why isn't the re-render process saved in this, or other similar, cases. What were we missing if we dont re-render?
React doesn't re-render elements in the sense that it creates a new one which replaces the old. In your example it simply updates the value.
React will only ever remove an element if it was replaced with one of another type. If it's of the same type, it will simply update the missing or altered attributes. You should have a read about Reacts Reconciliation on the official docs.
Elements Of Different Types
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch. Going from <a> to <img>, or from <Article> to <Comment>, or from <Button> to <div> - any of those will lead to a full rebuild.
DOM Elements Of The Same Type
When comparing two React DOM elements of the same type, React looks at the attributes of both, keeps the same underlying DOM node, and only updates the changed attributes.
Demo
I have prepared a demo below. It's a combination of React with jQuery just to demonstrate this.
When you type some text into the textarea, the state is updated and the element value is updated to show what you typed. This is done with React.
The button is outside the React scope completely. There is an eventlistener written with jQuery which randomly alters the elements css background color.
If the component was replaced each time React updated the state value of the component (i.e when we type inside the textarea), the background color would be lost since we would be talking about a newly created DOM element.
However, as you can see, this is not the case -- the background stays.
class Greeting extends React.Component {
constructor() {
super();
this.state = {txt: ""};
}
updateTextarea = (e) => {
this.setState({txt: e.target.value});
}
render() {
return <textarea onChange={this.updateTextarea}>{this.state.txt}</textarea>;
}
}
ReactDOM.render(<Greeting />, document.getElementById('app'));
$("#btn").on("click", function() {
var colors = ["red", "green", "lightblue", "grey", "pink", "orange"];
var rand = Math.floor(Math.random() * 6);
$("#app textarea").css("background", colors[rand]);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
<button id="btn">Randomize color!</button>

How to wait and fade an element out?

I have an alert box to confirm that the user has successfully subscribed:
<div className="alert alert-success">
<strong>Success!</strong> Thank you for subscribing!
</div>
When a user sends an email, I'm changing the "subscribed" state to true.
What I want is to:
Show the alert box when the subscribed state is true
Wait for 2 seconds
Make it fade out
How can I do this?
May 2021 update: as tolga and Alexey Nikonov correctly noted in their answers, it’s possible to give away control over how long the alert is being shown (in the original question, 2 seconds) to the transition-delay property and a smart component state management based on the transitionend DOM event. Also, hooks are these days recommended to handle component’s internal state, not setState. So I updated my answer a bit:
function App(props) {
const [isShowingAlert, setShowingAlert] = React.useState(false);
return (
<div>
<div
className={`alert alert-success ${isShowingAlert ? 'alert-shown' : 'alert-hidden'}`}
onTransitionEnd={() => setShowingAlert(false)}
>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={() => setShowingAlert(true)}>
Show alert
</button>
(and other children)
</div>
);
}
The delay is then specified in the alert-hidden class in CSS:
.alert-hidden {
opacity: 0;
transition: all 250ms linear 2s; // <- the last value defines transition-delay
}
The actual change of isShowingAlert is, in fact, near-instant: from false to true, then immediately from true to false. But because the transition to opacity: 0 is delayed by 2 seconds, the user sees the message for this duration.
Feel free to play around with Codepen with this example.
Since React renders data into DOM, you need to keep a variable that first has one value, and then another, so that the message is first shown and then hidden. You could remove the DOM element directly with jQuery's fadeOut, but manipulating DOM can cause problems.
So, the idea is, you have a certain property that can have one of two values. The closest implementation is a boolean. Since a message box is always in DOM, it's a child of some element. In React, an element is result of rendering a component, and so when you render a component, it can have as many children as you want. So you could add a message box to it.
Next, this component has to have a certain property that you can easily change and be completely sure that, as soon as you change it, the component gets re-rendered with new data. It's component state!
class App extends React.Component {
constructor() {
super();
this.state = {
showingAlert: false
};
}
handleClickShowAlert() {
this.setState({
showingAlert: true
});
setTimeout(() => {
this.setState({
showingAlert: false
});
}, 2000);
}
render() {
return (
<div>
<div className={`alert alert-success ${this.state.showingAlert ? 'alert-shown' : 'alert-hidden'}`}>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={this.handleClickShowAlert.bind(this)}>
Show alert
</button>
(and other children)
</div>
);
}
}
Here, you can see that, for message box, either alert-shown or alert-hidden classname is set, depending on the value (truthiness) of showingAlert property of component state. You can then use transition CSS property to make hiding/showing appearance smooth.
So, instead of waiting for the user to click button to show the message box, you need to update component state on a certain event, obviously.
That should be good to start with. Next, try to play around with CSS transitions, display and height CSS properties of the message box, to see how it behaves and if the smooth transition happening in these cases.
Good luck!
PS. See a Codepen for that.
The correct way is to use Transition handler for Fade-in/out
In ReactJS there is synthetic event to wait till fade-out is finished: onTransitionEnd.
NOTE there are different css effects associated with different handlers. Fade is a Transition not an Animation effect.
Here is my example:
const Backdrop = () => {
const {isDropped, hideIt} = useContext(BackdropContext);
const [isShown, setState] = useState(true);
const removeItFromDOM = () => {
debugger
setState(false)
};
return isShown
? <div className={`modal-backdrop ${isDropped ? 'show' : ''} fade` } onClick={hideIt} onTransitionEnd={removeItFromDOM}/>
: null
}
An other way is to solve this with a CSS3 transition.
https://www.tutorialspoint.com/css/css_animation_fade_out.htm
You can add a new class to the alert (like .hidden) and then you can relate .hidden with the class you defined for the alert.
alert.hidden{
// Here you can define a css transition
}
In this solution you don't have to add a setInterval or anything, since css3 transitions already process it on browser render.

React: removing a css className breaks transition

I have a ajax transition which is applyed to a DOM element by setting the className "fetching" in the render()-Method whenever the Component fetches data from the server. After fetch() returns, I let that className be removed so the Component can transition back to its original appearance.
It works like this:
fetchStuff() {
this.setState({fetching: true})
fetch(stuff)
.then(answer => {
// do stuff with the answer
this.setState({fetching: false})
)
}
// and then ...
render() {
let {fetching} = this.state
return <div className={'my-component' + (fetching ? ' fetching' : '')}></div>
}
.my-component {
transition: all .3s;
opacity: 1;
}
.my-component.fetching {
opacity: 0;
}
The start transition works fine! But when React removes the "fetching" className, the Component jumps to its original appearance without transition.
What can I do?
Thanks for your help :)
UPDATE:
Somehow this problem occurs more often in Chrome than in Firefox.
UPDATE 2:
Preventing unnecessary rerenders by using shouldComponentUpdate() on the parent Component made it better but still the transition sometimes skips or is way too fast. But sometimes it is now smooth as it should be.

React rerender only one child

in my form i have a few dropdown components. Whenever first dropdown option changes i want to update props for the second dropdown and rerender it. My code looks like this
handleProjectChange(option) {
//this.setState({ selectedProject: option })
this.refs.phase.props = option.phases;
//this.refs.forceUpdate()
this.refs.phase.render()
}
render() {
var projectOptions = this.projectOptions
var defaultProjectOption = this.state.selectedProject
var phaseOptions = defaultProjectOption.phaseOptions
var defaultPhaseOption = phaseOptions[0]
var workTypeOptions = api.workTypes().map(x => { return { value: x, label: x } })
var defaultWorkTypeOption = workTypeOptions[0]
return (
<div>
<Dropdown ref='project' options={projectOptions} value={defaultProjectOption} onChange={this.handleProjectChange.bind(this)} />
<Dropdown ref='phase' options={phaseOptions} value={defaultPhaseOption} />
<Dropdown options={workTypeOptions} value={defaultWorkTypeOption} />
<button className="btn btn-primary" onClick={this.handleAddClick.bind(this)}>Add</button>
</div>
)
}
But props are not changed, so it rerenders the same options. At the moment im just rerendering entire form by setting new state on it. Is there any way to rerender only one child/Dropdown with new props?
The way to do this is to put the selected option in first dropdown selectedProject in state.
And inside your render function, fetch/ populate the options in the second dropdown, dependent on the selected project.
Flow will then be:
User selects an option in the first dropdown.
This triggers handleProjectChange()
Inside handleProjectChange(), the newly selected option is put in state, by a this.setState() call
Because state changed, react re-runs the entire render() function.
Under the hood, react figures out that only the second dropdown has changed, so react will only re-render the second drop-down on your screen/ in the DOM.
Although React does have a reconciliation algorithm that dynamically checks whether each component should be rerenader or not in every rendering of its parent, it doesn't always work as we intended.
https://reactjs.org/docs/reconciliation.html
For this kind of issues, you have two options. You can use either React.pureComponent or React.useMemo().

Resources