I am having trouble checking the active state of an element attribute. I tried below but it returned false even though the element has the attribute in an active state - (.c-banner.active is present)
ngAfterViewInit() {
const bannerElm = document.getElementById("banner");
const isActive = bannerElm && bannerElm.getAttribute("class")
.indexOf("c-banner.active") !== -1; // returned false
}
Why you not use classList and contains?
classList.contains();
ngAfterViewInit() {
const bannerElm = document.getElementById("banner");
const isActive = bannerElm && bannerElm.classList.contains('c-banner') && bannerElm.classList.contains('active');
}
You can use classList.contains method to check if the element has the active class.
ngAfterViewInit() {
setTimeout(() => {
const isActive = bannerElm &&
bannerElm.classList.contains('c-banner') &&
bannerElm.classList.contains('active');
}, 1000);
}
[UPDATED] wrap it within setTimeout() and it worked! In case anyone else is stuck with the component initialization orders issue that I had previously.
Aside from what's the best way of actually accessing the value of the class attribute, judging by your comments the issue appears to be of asynchronous nature.
To avoid using a hackish setTimeout solution, I would recommend to apply a mutation observer and react to changes of the attribute accordingly.
Here's one way on how to do it.
PS: Edited the answer to make it more suitable for what you're trying to achieve. In the end this won't make a huge difference other than that in case the banner state is changed before the debounce time runs out, the subject emits right away and you potentially save some time waiting as compared to using setTimeout
bannerIsActive$ = new BehaviorSubject<boolean>(false);
ngAfterViewInit() {
const banner = document.getElementById('banner');
const observer = new MutationObserver((mutations: MutationRecord[]) => {
const mutation = mutations[0];
const classList: DOMTokenList = mutation.target['classList'];
this.bannerIsActive$.next(mutation.attributeName === 'class' && classList.contains('active'));
});
observer.observe(banner, {
attributes: true
});
this.bannerIsActive$.pipe(
debounce(isActive => timer(isActive ? 0 : 1000)),
take(1)
).subscribe(isActive => {
// do something with the banner state
});
}
you should work with ViewChild rather then Access your DOM directly.
both will work, but this is the strict way to do it.
#ViewChild('banner') input;
ngAfterViewInit() {
var classAttributes= this.input.nativeElement.getAttribute('class');
var result=classAttributes&&classAttributes('c-banner')
}
Related
Solved Thank you for your help
I am setting props of component
<Component myprops={state_variable}/>
The problem is that when I am creating the component and setting the props the state variable does not exist yet and my code breaks. What can I do to solve this problem? In addition when I change the state the prop is not updated.
<ServiceTicket
showOverlay={Tickets_disabled_withError[ticket_num]?.isDisabled}
showSelectedError={Tickets_disabled_withError[ticket_num]?.showError}
/>
My intial state initial variable:
const [Tickets_disabled_withError,setTickets_disabled_withError] = useState({})
I am trying to call function that will update state and change value that props is equal to.
const OverLayOnAll = (enable) =>
{
let tempobject = Tickets_disabled_withError
for (let key in tempobject)
{
if (enable == "true")
{
tempobject[key].isDisabled = true
}
else if (enable == "false")
{
tempobject[key].isDisabled = false
}
}
setTickets_disabled_withError(tempobject)
}
I fixed the issue. Thank you so much for your help. I had to set use optional chaining ?. and also re render the component.
The value exists. It's just that the value itself is undefined. You need to set an initial value when defining your state
const [statevariable, setstatevariable] = useState({
somekey: {
isDisabled: false // or whatever the initial value should be
}
}) // or whatever else you need it to be
For your second problem, you are using the same pointer. JavaScript does equality by reference. You've transformed the existing value, so React doesn't detect a change. The easiest way to fix this is to create a shallow copy before you start transforming
let tempobject = {...Tickets_disabled_withError}
Your question isn't very clear to me, but there's a problem in your setTickets_disabled_withError call.
When you update a state property (ticketsDisabledWithError) using its previous value, you need to use the callback argument.
(See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)
overlayAll = (enable)=> {
setTicketsDisabledWithError((ticketsDisabledWithError)=> {
return Object.keys(ticketsDisabledWithError).reduce((acc,key)=> {
acc[key].isDisabled = (enabled=="true");
return acc;
}, {}); // initial value of reduce acc is empty object
})
}
Also, please learn JS variable naming conventions. It'll help both you, and those who try to help you.
can someone tell me why is this "upvote" onClick handler firing twice?
the logs would indicate it's only running once but the score it controls increases by 2
export default class Container extends Component {
constructor(props) {
super(props);
this.state = {
jokes: [],
};
this.getNewJokes = this.getNewJokes.bind(this);
this.retrieveJokes = this.retrieveJokes.bind(this);
this.upVote = this.upVote.bind(this);
}
upVote(id) {
this.setState(state => {
//find the joke with the matching id and increase score by one
const modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
console.log(modifiedJokes);
return { jokes: modifiedJokes };
});
}
render() {
return (
<div>
<h1>Container</h1>
{this.state.jokes.map(joke => (
<Joke
key={joke.id}
id={joke.id}
joke={joke.joke}
score={joke.score}
upVote={this.upVote}
downVote={this.downVote}
/>
))}
</div>
);
}
}
on the other hand if I rewrite the handler this way, then it fires only once
upVote(id) {
const modifiedJokes = this.state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
this.setState({ jokes: modifiedJokes });
};
My best guess is that in the first case, you are also modifying the state directly, when you do joke.score = joke.score + 1;
Because you are doing this mapping directly on state array variable, and in Javascript, when using array, you are only working with pointer to that array, not creating a copy of that array.
So the mapping function probably takes a shallow copy of the array, and there's where problem happens.
You can use lodash to create a deep copy of the state array before you going to work with it, which will not cause your problem:
https://codesandbox.io/s/great-babbage-lorlm
This doesn't work because React uses synthetic events which are reused when it’s handling is done. Calling a function form of setState will defer evaluation (this can be dangerous when the setState call is event dependent and the event has lost its value).
So this should also work:
this.setState((state,props) => ({ jokes: modifiedJokes}));
I can't locate my source at the moment but, I remember coming across this a while back. I'm sure looking through the React Docs can provide a more in depth explanation
I found out what was wrong. I'll post an answer just in case anyone happens to stumble into this upon encountering the same problem.
When in debug mode react will run certain functions twice to discourage certain operations that might result in bugs. One of these operations is directly modifying state.
when doing
this.setState(state => {
//find the joke with the matching id and increase score by one
const modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
return joke;
});
console.log(modifiedJokes);
return { jokes: modifiedJokes };
the map function returns a new array where every elements points to the same element in the original array. therefore by doing
state.jokes.map(joke => {
if (joke.id === id) {
joke.score = joke.score + 1;
}
I was effectively directly modifying the state. React runs the setstate function twice in debug mode to weed out this kind of operation.
so the correct "react way"of modifying the attribute of an object nested in an array is to do this instead
this.setState(state =>{
let modifiedJokes = state.jokes.map(joke => {
if (joke.id === id) {
return {...joke, score:joke.score+1}
}
else{ return joke}
})
return {jokes:modifiedJokes}
})
this way when encountering an element with the correct ID a copy is made of that specific element and the score of that copy is increased by one which doesn't impact the actual element which is still in the state left untouched until it is modified by react committing the new state
Is there a good way to check if not completed Observable is empty at that exact time?
let cache = new ReplaySubject<number>(1);
...
// Here I want to know if 'cache' still empty or not. And, for example, fill it with initial value.
cache.isEmpty().subscribe(isEmpty => {
if (isEmpty) {
console.log("I want to be here!!!");
cache.next(0);
}
});
// but that code does not work until cache.complete()
Actually, it's not that simple and the accepted answer is not very universal. You want to check whether ReplaySubject is empty at this particular point in time.
However, if you want to make this truly compatible with ReplaySubject you need to take into account also windowTime parameter that specifies "time to live" for each value that goes through this object. This means that whether your cache is empty or not will change in time.
ReplaySubject has method _trimBufferThenGetEvents that does what you need. Unfortunately, this method is private so you need to make a little "hack" in JavaScript and extend its prototype directly.
import { ReplaySubject } from 'rxjs';
// Tell the compiler there's a isNowEmpty() method
declare module "rxjs/ReplaySubject" {
interface ReplaySubject<T> {
isNowEmpty(): boolean;
}
}
ReplaySubject.prototype['isNowEmpty'] = function() {
let events = this._trimBufferThenGetEvents();
return events.length > 0;
};
Then using this ReplaySubject is simple:
let s = new ReplaySubject<number>(1, 100);
s.next(3);
console.log(s.isNowEmpty());
s.next(4);
setTimeout(() => {
s.next(5);
s.subscribe(val => console.log('cached:', val));
console.log(s.isNowEmpty());
}, 200);
setTimeout(() => {
console.log(s.isNowEmpty());
}, 400);
Note that some calls to isNowEmpty() return true, while others return false. For example the last one returns false because the value was invalidated in the meantime.
This example prints:
true
cached: 5
true
false
See live demo: https://jsbin.com/sutaka/3/edit?js,console
You could use .scan() to accumulate your count, and map that to a boolean whether it's nonzero. (It takes a second parameter for a seed value which would make it start with a 0, so it always reflects the current count.)
I've also added a .filter() instead of an if statement to make it cleaner:
let cache = new ReplaySubject<number>(1);
cache
.map((object: T) => 1)
.scan((count: number, incoming: number) => count + incoming, 0)
.map((sum) => sum == 0)
.filter((isEmpty: boolean) => isEmpty)
.subscribe((isEmpty: boolean) => {
console.log("I want to be here!!!");
cache.next(0);
});
You could use takeUntil():
Observable.of(true)
.takeUntil(cache)
.do(isEmpty => {
if (isEmpty) {
console.log("I want to be here!!!");
cache.next(0);
}
})
.subscribe();
However this will just work once.
Another way would be to "null" the cache and initialize it as empty by using a BehaviorSubject:
let cache = new BehaviorSubject<number>(null as any);
...
cache
.do(content => {
if (content == null) {
console.log("I want to be here!!!");
cache.next(0);
}
})
.subscribe();
And of course you could initialize the cache with some default value right away.
startWith
let cache = new ReplaySubject<number>(1);
isEmpty$ = cache.pipe(mapTo(false), startWith(true));
This says:
Whatever the value is emitted by cache - map it to false. (because it isn't empty after an emission)
Start with true if nothing has been emitted yet (because that means it's empty)
I have an editor on a page and save button. I want save button only to appear if there are changes (since last save). So, when you save, i set this.setState({hasChanges: false}) and in onChange function i set hasChanges to true.
And that's all working fine. The issue is that editor will fire onChange event when editor gain focus (but content is not changed yet). And, that is expected behaviour according to their documentation
The event listeners within the component will observe focus changes
and propagate them through onChange as expected, so state and DOM will
remain correctly in sync.
Is there some way to know if content has changed inside onChange method, or only selection is changed?
I posted this answer to your question on github earlier today, but I'll add it here as well for future reference:
You're right in that onChange is called every time the editorState changes. And since the editorState holds both the selectionState and the contentState, it will change both when there's a content edit, and when the selection/focus changes.
If you want to know if the change that triggered the onChange method was in the contentState or in the selectionState, you could compare them with their old values:
function onChange(newState) {
const currentContentState = this.state.editorState.getCurrentContent()
const newContentState = newState.getCurrentContent()
if (currentContentState !== newContentState) {
// There was a change in the content
} else {
// The change was triggered by a change in focus/selection
}
}
Now, if you know that the change was in the contentState, you can get some more info by calling newState.getLastChangeType(). It should return any of these values (unless you, or some plugin you've added, have created new change types).
However, sometimes just the conditional if (currentContentState !== newContentState) not work and not detect the change for cases like when you to modify the content state using Entity.mergeData together with forceSelection because this change is stored inside entity and is not exposed in contentState.
So you could do something like that additionaly.
this.currentEntityMap = null;
function onChange(newEditorState) {
const currentContentState = editorState.getCurrentContent();
const newContentState = newEditorState.getCurrentContent();
const newContentStateRaw = convertToRaw(newContentState);
const newEntityMap = newContentStateRaw ? newContentStateRaw.entityMap : null;
//so if you have the two entity maps, you can to compare it, here an example function.
const isEntityMapChanged = this.isEntityMapChanged(newEntityMap);
this.currentEntityMap = newEntityMap;
}
isEntityMapChanged(newEntityMap) {
let isChanged = false;
if (this.currentEntityMap) {
loop1:
for (const index of Object.keys(newEntityMap)) {
if (this.currentEntityMap[index] && newEntityMap[index].type === this.currentEntityMap[index].type) {
loop2:
for (const prop of Object.keys(newEntityMap[index].data)) {
if (newEntityMap[index].data[prop] !== this.currentEntityMap[index].data[prop]) {
isChanged = true;
break loop1;
}
}
} else {
isChanged = true;
break loop1;
}
}
}
return isChanged;
}
then to used the flag isEntityMapChanged and the first conditional for to do the save.
you should use code follow:
const onChange = newState => {
if (
// when content change or line style change will change the state
!newState.getCurrentContent().equals(editorState.getCurrentContent()) ||
!newState
.getCurrentInlineStyle()
.equals(editorState.getCurrentInlineStyle())
) {
setEditorState(newState);
}
};
if you just use content equal ,then line style change will not on effect.
For the purposes of debugging in the console, is there any mechanism available in React to use a DOM element instance to get the backing React component?
This question has been asked previously in the context of using it in production code. However, my focus is on development builds for the purpose of debugging.
I'm familiar with the Chrome debugging extension for React, however this isn't available in all browsers. Combining the DOM explorer and console it is easy to use the '$0' shortcut to access information about the highlighted DOM element.
I would like to write code something like this in the debugging console:
getComponentFromElement($0).props
Even in a the React development build is there no mechanism to use maybe the element's ReactId to get at the component?
Here's the helper I use: (updated to work for React <16 and 16+)
function FindReact(dom, traverseUp = 0) {
const key = Object.keys(dom).find(key=>{
return key.startsWith("__reactFiber$") // react 17+
|| key.startsWith("__reactInternalInstance$"); // react <17
});
const domFiber = dom[key];
if (domFiber == null) return null;
// react <16
if (domFiber._currentElement) {
let compFiber = domFiber._currentElement._owner;
for (let i = 0; i < traverseUp; i++) {
compFiber = compFiber._currentElement._owner;
}
return compFiber._instance;
}
// react 16+
const GetCompFiber = fiber=>{
//return fiber._debugOwner; // this also works, but is __DEV__ only
let parentFiber = fiber.return;
while (typeof parentFiber.type == "string") {
parentFiber = parentFiber.return;
}
return parentFiber;
};
let compFiber = GetCompFiber(domFiber);
for (let i = 0; i < traverseUp; i++) {
compFiber = GetCompFiber(compFiber);
}
return compFiber.stateNode;
}
Usage:
const someElement = document.getElementById("someElement");
const myComp = FindReact(someElement);
myComp.setState({test1: test2});
Note: This version is longer than the other answers, because it contains code to traverse-up from the component directly wrapping the dom-node. (without this code, the FindReact function would fail for some common cases, as seen below)
Bypassing in-between components
Let's say the component you want to find (MyComp) looks like this:
class MyComp extends Component {
render() {
return (
<InBetweenComp>
<div id="target">Element actually rendered to dom-tree.</div>
</InBetweenComp>
);
}
}
In this case, calling FindReact(target) will (by default) return the InBetweenComp instance instead, since it's the first component ancestor of the dom-element.
To resolve this, increase the traverseUp argument until you find the component you wanted:
const target = document.getElementById("target");
const myComp = FindReact(target, 1); // provide traverse-up distance here
For more details on traversing the React component tree, see here.
Function components
Function components don't have "instances" in the same way classes do, so you can't just modify the FindReact function to return an object with forceUpdate, setState, etc. on it for function components.
That said, you can at least obtain the React-fiber node for that path, containing its props, state, and such. To do so, modify the last line of the FindReact function to just: return compFiber;
Here you go. This supports React 16+
window.findReactComponent = function(el) {
for (const key in el) {
if (key.startsWith('__reactInternalInstance$')) {
const fiberNode = el[key];
return fiberNode && fiberNode.return && fiberNode.return.stateNode;
}
}
return null;
};
I've just read through the docs, and afaik none of the externally-exposed APIs will let you directly go in and find a React component by ID. However, you can update your initial React.render() call and keep the return value somewhere, e.g.:
window.searchRoot = React.render(React.createElement......
You can then reference searchRoot, and look through that directly, or traverse it using the React.addons.TestUtils. e.g. this will give you all the components:
var componentsArray = React.addons.TestUtils.findAllInRenderedTree(window.searchRoot, function() { return true; });
There are several built-in methods for filtering this tree, or you can write your own function to only return components based on some check you write.
More about TestUtils here: https://facebook.github.io/react/docs/test-utils.html
i wrote this small hack to enable access any react component from its dom node
var ReactDOM = require('react-dom');
(function () {
var _render = ReactDOM.render;
ReactDOM.render = function () {
return arguments[1].react = _render.apply(this, arguments);
};
})();
then you can access any component directly using:
document.getElementById("lol").react
or using JQuery
$("#lol").get(0).react
In case someone is struggling like me to access React component/properties from a chrome extension, all of the above solutions are not going to work from chrome extension content-script. Rather, you'll have to inject a script tag and run your code from there. Here is complete explanation:
https://stackoverflow.com/a/9517879/2037323
Here is a small snippet i'm currently using.
It works with React 0.14.7.
Gist with the code
let searchRoot = ReactDom.render(ROOT, document.getElementById('main'));
var getComponent = (comp) => comp._renderedComponent ? getComponent(comp._renderedComponent) : comp;
var getComponentById = (id)=> {
var comp = searchRoot._reactInternalInstance;
var path = id.substr(1).split('.').map(a=> '.' + a);
if (comp._rootNodeID !== path.shift()) throw 'Unknown root';
while (path.length > 0) {
comp = getComponent(comp)._renderedChildren[path.shift()];
}
return comp._instance;
};
window.$r = (node)=> getComponentById(node.getAttribute('data-reactid'))
to run it, open Devtools, highlight an element you want to examine, and in the console type : $r($0)
I've adapted #Venryx's answer with a slightly adapted ES6 version that fit my needs. This helper function returns the current element instead of the _owner._instance property.
getReactDomComponent(dom) {
const internalInstance = dom[Object.keys(dom).find(key =>
key.startsWith('__reactInternalInstance$'))];
if (!internalInstance) return null;
return internalInstance._currentElement;
}
React 16+ version:
If you want the nearest React component instance that the selected DOM element belongs to, here's how you can find it (modified from #Guan-Gui's solution):
window.getComponentFromElement = function(el) {
for (const key in el) {
if (key.startsWith('__reactInternalInstance$')) {
const fiberNode = el[key];
return fiberNode && fiberNode._debugOwner && fiberNode._debugOwner.stateNode;
}
}
return null;
};
They trick here is to use the _debugOwner property, which is a reference to the FiberNode of the nearest component that the DOM element is part of.
Caveat: Only running in dev mode will the components have the _debugOwner property. This would not work in production mode.
Bonus
I created this handy snippet that you can run in your console so that you can click on any element and get the React component instance it belongs to.
document.addEventListener('click', function(event) {
const el = event.target;
for (const key in el) {
if (key.startsWith('__reactInternalInstance$')) {
const fiberNode = el[key];
const component = fiberNode && fiberNode._debugOwner;
if (component) {
console.log(component.type.displayName || component.type.name);
window.$r = component.stateNode;
}
return;
}
}
});
Install React devtools and use following, to access react element of corresponding dom node ($0).
for 0.14.8
var findReactNode = (node) =>Object.values(__REACT_DEVTOOLS_GLOBAL_HOOK__.helpers)[0]
.getReactElementFromNative(node)
._currentElement;
findReactNode($0);
Ofcourse, its a hack only..