instant html class change by DOM manipulation in React - javascript

In React app I have a code to toggle html element's (let's call it element A) class when pushing a button. I do it by mutating a state which contains settings for the whole app. Then when the element A is rendered it checks the appropriate setting and apply or not apply the class to it.
This works, but it takes a long time to re-render everything and thus there is a lag between click and changes applied. So I thought to use a little hack here with classic DOM manipulation to first change class (to see changes instantaneously) and then let React re-render everything.
The issue I have is that the changes are not applied by DOM manipulation, but only later after re-render. When I comment the setState line, the changes are visible immediately.
My question is, how to achieve instant class change and what could be wrong here?
const handleCollapseToggle = (i, el) => {
const helpToggler = document.getElementById(el);
helpToggler.classList.toggle('is-hidden');
console.log('test',helpToggler);
const newCollapsed = Array.from(settings.collapsed);
newCollapsed[i] = !newCollapsed[i];
const newSettings = {
...settings,
collapsed:newCollapsed
}
setSettings(newSettings);
//setTimeout(setSettings(newSettings), 10000);
}
I have also tried the setTimeout to apply a new state (please see on the last commented line of code), but that did not work and even did take less then 10 seconds to re-render. Also the console.log('test',helpToggler) logs out the 'test' word first and then the element itself later after re-render (on the same console log line).

Related

React, wait for DOM to update before unmounting?

I have a component that uses a js library. Since it's vanilla js I've added a bunch of dynamic eventListners that I want to remove when unmounting the component. I've set up a function to run on Blur or when clicking out side of the component so it would run document.getElementById and then clone that element and replace it. Right after that I update the state to let Parent component know to not render the component anymore so it unmounts.
What seems to be happening is the code to get, clone the element and replace it isn't happening right away and so the state to unmount is running and by the time the cloning and replacing happens the component i already unmounted so it cannot find that element in the DOM anymore. How can I can avoid this
const cleanUp = () => {
const element = document.getElementById(id);
const clone = element.cloneNode(true);
element.parentNode.replaceChild(clone, element);
setUnMount(true);
};

Concurrent-mode and sync flushing of setState in click handlers

Does the upcoming concurrent-mode break the old guarantee, that setState updates within a click handler are flushed synchronously at the event boundary?
If i have e.g. a button, that should only ever be pressed once, a supposedly working pattern was to "just set the state to disabled in the click handler":
let counter = 0;
const C = () => {
const [disabled, setDisabled] = React.useState(false);
const handler = React.useCallback(
() => { setDisabled(true); counter++; },
[], // setDisabled is guaranteed to never change
);
return (<button onClick={handler} disabled={disabled}>click me</button>);
};
// Assert: `counter` can never be made >1 by clicking the button with one C
This pattern used to be guaranteed to work (at least given that setting the disabled-attribute prevents any further click events, which seems to be the case). The biggest related question i could find discusses this, and also shows a more or less obvious alternative (and easier to prove it works), of using a ref (unlike the answer in the linked question, maybe rather a boolean ref, but same idea, it's always sync).
Side questions: Is this information up-to-date, or did something change? It's more than three years old after all. It mentions "interactive events (such as clicks)", what are the others?
However, in concurrent-mode, rendering can be paused, which i interpret as "the js thread will be released", to allow potential key presses or whatever events to trickle in, and in that phase, additional click events could also happen, before the next render disables the button. Is therefore the way to go to use some kind of ref, or maybe explicitly adding ReactDOM.flushSync?
My current understanding of how concurrent mode works is this:
1 - a re-render starts
2 - hooks are called, they change internal state
3a - re-render is suspended
4a - internal state changes are rolled back
OR
3b - re-render is not suspended
4b - internal state changes are commited
useCallback is a thin wrapper over useMemo and uses "internal state" to save the cached value. (4a) is the key here, and from what I understand your solution is not guaranteed to work anymore.
The useRef (with a boolean flag value) solution has the same issue too because you're not guaranteed that the new value of the ref is actually going to be "commited" when re-rendering is suspended.
The useRef solution where you keep a ref to the DOM button element and directly manipulate the disabled attribute will still work even in concurrent mode. React has no way of blocking you from directly manipulating DOM.
"suspending" means reverting "internal state" + not applying the generated DOM manipulations, does not mean any side effects (like manipulating DOM directly) can be affected.
flushSync will not help either, it simply forces re-renders, does not guarantee that the current render won't be suspended.
As far as I know the setState call was always async, and you never had a way to warranty that the button will be disabled right after the click. Also there is no such thing as concurrency in JS, it has single thread, the problem is that the render can happen latter than you expect so you can receive another click until React made re-render for you.
If you need to fire the logic only once I would advice to use useRef hook and when you need to make sure that we have not clicked the button just check the value.
const isDisabled = useRef(false);
const onClick = () => {
if (!isDisabled.current) {
isDisabled.current = true;
}
}

How to update a state object in the DOM from componentDidMount() (not componentDidUpdate())?

I'm trying to conditionally show or not show per say a button based on data that I receive from clicking on a point. I realized that regular jquery functions to add a class don't really work in React. So I figured I could store strings in the state like
this.state: {
hidden_components: {
add_comment: "hide"
}
}
This way I can conditionally show or hide a button by
<button className={this.state.hidden_components.add_comment}> Add Comment </button>
After the render() I have more or less:
componentDidMount() {
this.state.g = new Dygraph
this.state.modal = new Modal
this.state.modal.setContent(use some ID here to reference a div that is hidden but will show up in the modal)
const set_hidden_container = () => {
// I'm just going to use this = notation instead of setState()
// this is supposed to reset the
this.state.hidden_components = "hide"
if (check_comment(this.state.points[at some index].value)) {
this.state.hidden_components = "show"
}
}
this.state.g.updateOptions( {
pointClickCallback: (event, p) => {
console.log("i clicked a point on the graph")
this.setState({
currentPoint: p
})
set_hidden_containers()
// force update
this.setState({
currentPoint: p
})
// I want the modal to open a div of things that only show jsx based on logic in set_hidden_container()
this.state.modal.open()
}
}
componentDidUpdate() {
// logic goes here for like event listeners and anything that queries the DOM after initialization
}
Then in componentDidMount() I have a function that depending on the data received from clicking on a point I do the following:
1) reset all the classes stored in the state to "hide"
2) based on conditions set some of them to "show"
3) concatenate all the classes stored in the state with various styling classes
UPDATE:
I've long since found an easier solution to this problem, however, I'm guessing some people might have similar issues. Therefore, I'll update this question with more psuedocode and a workaround: maybe someone down the line can solve this. This component is particularly frustrating to work with because I haven't been able to make it as modular as I want because of the particular library I'm working with. There are actually about a 1000 lines in this component (I know I know not good).
WORKAROUND:
For those of you who are having trouble with a component's lifecycle in dynamically setting parts of the DOM but don't want to use global variables to set classNames, jquery functions, or use react syntax to show components containing the content I recommend you do the following.
You can still have a set_hidden_container() set content dynamically, you just have to set things based on an id with innerHTML instead of setting a state object to be a string "show". The important thing is, however, that for every time you need to dynamically change content you reset these references to be empty as well as force an update. You can simply change the state of anything and then in componentDidUpdate() you can insert 1) a conditional to check if the innerHTML was actually set or not (since you're not always going to be displaying everything) and 2) within that conditional you can set whatever logic you want associated with the content showing on the page.
componentDidMount is invoked immediately after a component is mounted. If you want to set classNames based on clicks, I would put that logic in componentDidUpdate, which is invoked after updating occurs.

setState vs refs in react.js

I created tabs in react and and now on click I have to change the class of the tabs the tabs classes may be as follows:
1:active
2:previousActive
3:alreadySelected
On click of a tab class become active and check whether it is selected before or not using alreadySelected class and active class from the last active tab is remove and if it is not alreadySelected then add alreadySelected.
Code of one tab in react:
var TabBody = React.createClass({
getInitialState: function() {
return {
class: 'tabBody tab activeTab'
}
},
render: function() {
a.tabBody = this;
return (React.createElement('div', {
className: this.state.class,
ref: 'body',
onClick: handleTabClick
},
React.createElement('span', {}, "Body"))
);
}
});
In order to change the class of the tabs I am doing in two ways and want to know which is effective. Code style one:
var bodyClass = (a.tabBody.state.class).split(' ');
var sleeveClass = (a.tabSleeve.state.class).split(' ');
var neckClass = (a.tabNeck.state.class).split(' ');
if (data === 'tabBody') {
bodyClass.push('activeTab');
var str1 = program.arrayToString(bodyClass);
Interfaces.tabBody.setState({
class: str1
});
}
Code Style 2
a.tabBody.refs.body.classList.remove('activeTab');
a.tabBody.refs.body.classList.add('tabPreviewComplete');
a.tabSleeve.refs.body.classList.add('activeTab');
Which style is good for doing this and why?
The point of react is that you do not need to/ should not update DOM directly. The idea behind react is that you render react components (virtual DOM), and that you let react figure out if and how to update DOM.
Changing classes using refs is a very risky strategy: Your component's state is then no longer in sync with actual DOM, which could bring you into debugging nightmares later on. So I would pose that Code Style 2 (even though it works) violates react principles.
One of the few exceptions for using refs, is to add a listener to a DOM component after it is mounted.
The react way is to put the classNames in state.
And do a setState() to update.
And let react do the DOM update,
which is very likely to be way faster, cleaner, and easier to maintain than getting refs, and changing classNames.
ref means you are using the actual DOM and setState means you are saying to react that please update the specific attribute of the component.
every thing is maintain by react.
On the other hand if you use refs it means you are doing every thing your own and react have no concern to your attributes and properties you are updating.

Is my React component being recreated instead of updating?

I am trying to combine Angular and React.js. I have an work example project here I have seen a couple of ways to bring the Angular and React.js together. One of the methods I have seen is to create a directive and create the React component in the link function. For example in the first part of the project to generate the React version(in red) I am using
.directive('reactElementRepeater', function() {
return {
link: function(scope, element) {
var update_react = function(oldVal, newVal){ //Called every time one of the two values change
React.renderComponent(Demo_Element({
numberOfElements: scope.myModel.numberOfElem,
numberInElements: scope.myModel.numberInElem
}), element[0]);
}
scope.$watch('myModel.numberOfElem.length', update_react);
scope.$watch('myModel.numberInElem', update_react);
}
}
});
What I want and what should happen in a React enabled application is for something in the model to be updated, then that update is sent through React and it will alter the DOM as little as possible to reflect that change. It looks like that instead of updating a bit of the DOM this will Create a new React component each time with renderComponent.
React.renderComponent() instantiates the root component, starts the
framework, and injects the markup into a raw DOM element, provided as
the second argument.
Is it actually recreating the elements each time? If that is the case is there a way to alter this so that doesn't happen?
Just to be clear I know about ngReact, I just want to know other ways to speed up Angular with React.
Yes this is fine, it's not mounting the component multiple times.
When you call React.renderComponent() the second argument is the element which react should render the component to. So react notices if you are rendering the same component to a dom element that already contains a mounted instance of the component, and does not re-mount the entire component, it just updates the properties of it instead.
You can see this in action if you make a component with componentDidMount function defined. You'll notice that componentDidMount will only execute the first time renderComponent gets called. And afterwards, subsequent calls to renderComponent on the same target dom element will not call it because the component is already mounted. Likewise getDefaultState and getDefaultProps also only get called on the first renderComponent call.
If you're asking will the render function of the component be called every time the answer is yes. But this is how react works, you want the render function to get called because props might have changed. You can block it from being called by using shouldComponentUpdate (http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate) and returning false. However react developers recommend you don't use this to block render calls unless you have specific performance problems - most of the time it should be fine to just let the render call execute as it wont cause any slow dom updates unless things have actually changed.

Categories