Calling "document.getElementByID" in React Component vs. "ref" - javascript

I am trying to open a modal by changing the style properties of the modal, so it is visible. I works perfectly when I write a function like this:
modalAction(action) {
switch (action) {
case "open": {
const modal = document.getElementById("login-popup");
modal.style.opacity = 1;
modal.style.visibility = "visible";
}
}
}
Yet I have never seen react code like this. Insead I read a lot about refs that use Code like this:
<div className="login-popup" ref={(ref) => {this.loginPopup = ref}} id="login-popup">
But as far as I see it using refs will only alow me to access a virtual DOM elements in the same (or nested) components. The modal is a component of itself and is not nested it is calles in.
I could solve my problem using redux but writing a reducer-case as well as an action and producing an additional piece of state seems overkill for my sort of problem.
How can I access the modals style properties the right way if the modal is neither nested nor in the same component where it is called from? Thx

Related

Any way to use React refs with methods on the window object that require DOM elements?

I'm writing a React app that embeds lists of Tweets that it pulls from an API. I'm aware that there already exists react-twitter-embed and react-twitter-widgets but both of these proved to be very slow, both in terms of load time and visual performance when scrolling, compared to Twitter's own twttr javascript widgets.
The only problem with using Twitter's widget is that, in order to implement lazy loading such that the app does not actually make the call to Twitter's embed API until the div containing the Tweet is visible on the screen, it seems I have to do some direct DOM references which I understand is not good to do in React.
The "React-flavored" version I attempted first looked something like this:
Tweet component
const tweetDOMId = `twt-${tweet.twtId}`
const divDOMId = `div-${tweet.twtId}`
const tweetRef = createRef()
<div id={divDOMId} ref={tweetRef}>
<blockquote
id={tweetDOMId}
className='twitter-tweet'
data-dnt='true'
data-theme='dark'>
<a href={twtUrl}></a>
</blockquote>
</div>
Parent component
const loadTweet = (tweetRef) => {
window.twttr.widgets.load(tweetRef)
window.twttr.events.bind('rendered', (event) => {
setLoadedTweet(event.target.(...).tweetId)
})
}
However, the Twitter widgets.load() method was unable to find the appropriate element to turn into an embedded tweet this way. Instead, I had to write it like this:
Tweet component
const tweetDOMId = `twt-${tweet.twtId}`
<blockquote
id={tweetDOMId}
className='twitter-tweet'
data-dnt='true'
data-theme='dark'>
<a href={twtUrl}></a>
</blockquote>
Parent component
const loadTweet = (elementId) => {
window.twttr.widgets.load(document.getElementById(`${elementId}`))
window.twttr.events.bind('rendered', (event) => {
setLoadedTweet(event.target.(...).tweetId)
})
}
So I am wondering if there is any way to not use document.getElementById() in this situation? A way to supply window.twttr.widgets.load() with a reference to the element that it needs without querying the DOM?
Using document.getElementById() is what Twitter recommends, but that example in the link is clearly for vanilla js, not React.
You seem to be trying to load the ref directly, instead of ref.current.

Access $refs from other components not in the same level as current component

I'm working on a Vue application.
It has a header and then the main content.
Nesting and structure as below
TheHeader.vue -> TheLogin.vue
MainContent.vue -> ShoppingCart.vue -> OrderSummary.vue
I need to access an element in TheLogin.vue from OrderSummary.vue
this.$refs.loginPopover.$emit('open')
gives me an error "Cannot read property '$emit' of undefined" so obviously I am not able to access $refs from other components.
The question is how do I get hold of refs from other components?
Thanks in advance!
Edit 1 - Found out $refs works with only child components.
How do I access elements across components in different level?
You definitely don't want to be reaching through the hierarchy like that. You are breaking encapsulation. You want a global event bus.
And here's a secret: there's one built in, called $root. Have your OrderSummary do
this.$root.emit('openPopup');
and set up a listener in your TheLogin's created hook:
this.$root.on('openPopup', () => this.$emit('open'));
In general, you should try to avoid using refs.
For anyone who comes here later and wants to access $refs in parent component, not in this particular case for emitting events since event bus or a store would suffice but let's just say you want to access some element in parent to get it's attributes like clientHeight, classList etc. then you could access them like:
this.$parent.$parent.$refs //you can traverse through multiple levels like this to access $ref property at the required level
You can put a function like this on your component to do this. I put mine in a Mixin:
public findRefByName(refName) {
let obj = this
while (obj) {
if (obj.$refs[refName]) {
return obj.$refs[refName]
}
obj = obj.$parent
}
return undefined
}
I also added some accessors to help:
get mycomponent() {
return this.findRefByName('mycomponent')
}
And once that exists, you can access your component by simply doing:
this.mycomponent
Thanks for that tip Abdullah!
In my case I was looking for a sibling, so in case someone comes looking for that, here's an example:
var RefName='MyCoolReferenceName';
var MyRef,x;
for(x=0;x<this.$parent.$children.length;x++)
{
if(typeof this.$parent.$children[x].$refs[RefName] !='undefined')
MyRef=this.$parent.$children[x].$refs['LABEL_'+bGroupReady.ChildID];
}
if(typeof MyRef !='undefined')
MyRef.error=true;
PS - The reason I'm doing MyRef.error=true is because I was having ZERO luck with Quasar inputs and lazy-rules="ondemand". Turns out you can just set .error=true to activate the error message and the red highlighting and .clearValidation() event to clear it back out. In case someone is trying to do that as well!

How to get React setState to re-render for Foundation 6 Reveal (Modal)?

My issue has been touched in a few questions around the web but I don't think it's been holistically asked.
I am using Foundation 6 with React. Everything works by using
import $ from 'jquery';
window.jQuery = $;
let foundation = require('path/to/foundation.js');
then in componentDidUpdate(), I call $(document).foundation(). Also, the CSS is being called somewhere.
My problem is once I get the modal to open, I can't populate it with data using setState(). I think I understand that the DOM changes when the modal opens, thus causing issues but I was wondering if anyone has had success with the Reveal plugin? My code is like this:
getData() {
Facebook.get('/me/taggable_friends', function(error, response) {
$('#modal').foundation('open');
//setTimeout is just for testing sanity
let _this = this;
setTimeout(function() {
_this.setState({ friends: response.data });
}, 3000);
})
}
Again, everything works. I'm getting data back from Facebook, the state is updating, the modal is opening, I'm just not able to populate the modal and I have this as my markup:
<div id="modal" className="modal-classes-from-foundation" data-reveal>
{
this.state.friends.length > 0 &&
this.state.friends.map((friend, i) => {
return(
<div>{ friend.name }</div>
)
})
}
</div>
Also to note, this.state.friends is being set in the constructor as an empty array.
All the code is valid on my server (no errors), but I wrote this from memory so I didn't remember small details like class/path names
Things I tried
Using componentWillReceiveProps to force update
Setting state before calling modal open
I dont think your answer is so smart. AFAIK the current state of the art is to set a nodeReference using a setNode method and then have
ref={this.setNode}
in your element, so then you can call whatever you are calling with jQuery, passing it via argument the node so you can play with it, and use the React lifecycle hooks to sync your react world with your jQuery playground
componentWillMount() {
window.addEventListener('resize', this.handleWindowResize, false);
}
componentDidMount() {
//initialize your jQuery dom manipulation
}
shouldComponentUpdate(nextProps) {
if (!this.props.store.equals(nextProps)) {
//to update stuff
}
return false; //> dont update
}
componentWillUnmount() {
//
}
setNode(ref) {
this.nodeReference = ref;
}
After banging my head against a virtual desk for a day, I understand why it's tough getting Reveal to work with React, especially compared to the other Foundation elements.
Reveal is an overlay that lives inside of <body></body> but not inside <div id="app"></div>, or whatever you name the root div that your React app renders to. Since the overlay is outside of the "app", React has no control over it and sending states/props to it won't do anything.
What I ended up doing is a bit clever and could be looked down upon, but seemed very necessary. I took inspiration from BlackMutt in 2015 where he basically created a function to use jQuery for appending the modal's code and initializing it. Unfortunately, this means every other thing you do with the modal will need to use jQuery as well but the good news is that it's separated from the rest of the app. Here's a sample of what it looks like:
createListModal(items) {
let content = items.map((item, i) => {
return $('<div class="list-item"><div class="item-name">'+ item.name +'</div></div>');
});
let close = $('<button class="close-button" data-close aria-label="Close modal" type="button"><span aria-hidden="true">×</span></button>');
let modal = $('<div class="reveal" id="list-popup" data-reveal>').append(content).append(pagination).append(close);
$(modal).foundation();
$('#list-popup').foundation('open');
}
So all I did was call that method when I got my data from Facebook. It's pretty simple but if you've been in React for a while, you have to switch your brain back into thinking in jQuery again.

Decoupling functionality in React

I'm trying to figure out how I can decouple some React components -- specifically modals -- so that the components which will open the modals don't need to know how they are opened.
For example, in jQuery I could do something like:
var $modal = $(".modal");
var modals = {};
/* Gets a modal dialog
*/
function _get_modal(modal) {
if(modals[modal] === undefined) {
modals[modal] = $(".modal-dialog[data-modal=" + modal + "]");
}
return modals[modal];
}
/* Shows the modal page dimmer and the modal dialog
*/
function show_modal(modal) {
var $m = _get_modal(modal);
$modal.show();
$m.show();
return $m;
}
// Later in a click event listener somewhere...
show_modal("MyModal");
If I wanted to do this same thing in React it seems like I would need to pass down a function from my parent component to all of the children and the children's children so that they know how to trigger the modal.
Ideally, I'd like to abstract what I'm getting at to be able to implement a Command pattern.
have you looked at react-modal. It encapsulate already the modal. https://github.com/reactjs/react-modal. And even if you are not using it you might get an idea how to encapsulate things in react. One note: Command pattern is an OOP pattern, react has a functional approach. Investigate how to handle things more in the functional way. HTH

Find and manipulate a React.js component from the JavaScript console

When I work with JS I tend to whip out a console for the browser and manipulate values on the fly.
I have a page where I use React to render some components and I had the idea that it would be great to be able to manipulate it's state from the console to debug a design quirk which is only visible if the component is in a corner-case state.
I ran into problem that I was unable to get hold of a reference to my component.
I figured there might be a list of active components currently being rendered somewhere, but I was not able to find one on the React global object or anywhere else.
Is there an exposed reference to the components being rendered?
I'm rendering the component like:
<script>React.render(React.createElement(Comp, domElem))</script>
I could store a reference to the result of React.createElement() but it seems to be an antipattern. Also I'm using the ReactJS.NET library to handle server-side rendering for me so the whole React.render line is generated and is hard to modify.
My other idea was to create a mixin that makes the component explicitly expose itself on mount, like:
var ActiveComponents = [];
var debugMixin = {
componentDidMount: function () {
var id = this.getDOMNode().id;
ActiveComponents[id] = {
id: id,
getState: () => { return this.state; },
setState: (state) => { this.setState(state); },
comp: this
};
}
};
Are there drawbacks for an approach like this? Is this the same antipattern mentioned above?
Although being much cleaner than entangling these test hooks in the component code directly, adding a mixin is still a modification, and I would like to avoid that if possible.
The questions I hope to get answers for are bolded.
A workaround for this is to assign your object to the window object:
window.myStateObject = myStateObject
and then you can inspect it in the console:
window.myStateObject
There is a ReactJS extension for Chrome that may meet your needs https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
If that isn't good enough, React keeps track of all the mounted components in a private variable instancesByReactID. If you just want to access these for debugging, you could modify the React code and expose that variable as a global.

Categories

Resources