How to get document element when I use external script in react? - javascript

I'm using React to build my project. I made a chat button by using external script. And I'd like to disappear that button in some specific pages. I can't use a ref to access that button's element So I used document.getElementById.
But my problem is my code sometimes returns error. (I think when my code runs, chat button didn't run by external script.) How can I solve this problem?
useEffect(() => {
//access chat button element by using document.getElementById
const chatBtn = document.getElementById('ch-plugin-launcher');
//if it doesn't exist in current page, it returns.
if (!chatBtn) {
return;
}
//if a button exists, it will be hide.
chatBtn.classList.add('hide');
return () => {
chatBtn.classList.remove('hide');
};
}, []);

I think the error in return of useEffect. You return a function, which can be called at any time whenever the chat button does not exist. Add check for existing on the chat button in the useEffect return function. Other code looks well.
useEffect(() => {
// Your code
return () => {
document.getElementById('ch-plugin-launcher')?.classList?.remove('hide');
};
});

I think #0x6368656174 answwer is correct.
Just for more clarification why:
When exactly does React clean up an effect? React performs the cleanup
when the component unmounts. However, as we learned earlier, effects
run for every render and not just once. This is why React also cleans
up effects from the previous render before running the effects next
time. We’ll discuss why this helps avoid bugs and how to opt out of
this behavior in case it creates performance issues later below.
Source: https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1

Related

Assigning onclick on button that is outside of current react app (php) gives unexpected behaviour

We are builidng react components for a php app, it doesn't really have much intergration but there's a php save button that is should listen for and save onclick. But there's a catch, for some reason most of functions i'm trying to use are just not getting ran. I'm working with react-form-hook and trying to run the onsubmit() with my function inside.
handleSubmit((data) => {
handleSaveOrderProducts(data, selectedProductsRef.current);
});
To assign this function to the button i have to listen for the button clicks with id, i tried different approaches but none seems to fix the problem. Ways to listen i tried:
Using https://www.npmjs.com/package/react-detect-click-outside#features lib
const productInformationContainerRef = useDetectClickOutside({
onTriggered(event) {
// #ts-ignore
if (event.target?.id === "save-order-button") {
console.log("save order button");
handleSubmit((data) => {
handleSaveOrderProducts(data, selectedProductsRef.current);
});
}
},
});
What i get in result? The callback itself runs, but the function (handlesubmit) is not. Even if i put a console.log in callback with data there's just nothing displayed.
Different approaches from this stackoverflow thread, mostly everything related to functional react Detect click outside React component .
If you had experience in something like this or have any theoretical knowledge please comment :)strong text

Can I get away with not using useEffect in this instance?

I'm still learning React and the proper way to do things. In this React project I am using the TinyMCE editor: https://www.tiny.cloud/docs/tinymce/6/react-cloud/
I want to display a warning message about unsaved changes once the editor has been made "dirty" that is that the user has modified the default content you get on reload.
The component that contains the editor has the following code:
<Editor
apiKey='asdf'
onInit={(_evt, editor) => editorRef.current = editor}
onDirty={(_evt, _editor) => setEditorWasModified(true)}
...
</Editor>
Finally I have just put the code directly into the React component itself where it overwrites the window.onbeforeunload event:
const [editorWasModfied, setEditorWasModified] = useState(false);
const editorRef = useRef<TinyMCEEditor | null>(null);
window.onbeforeunload = () => {
if (editorWasModfied) {
return "You have unsaved changes. Are you sure you want to leave?";
}
};
This code seems to run and work well.
So is my question here is this the correct approach to do this? Or do I need to use an useEffect hook or something similar? If I understand useEffect correctly, it runs on every render which is not something I want. I know there is a second argument, but since it refers to the editorWasModified variable, I get a warning that the dependency array should contain it, which is false since I only want the useEffect once to update the window.onbeforeunload event.
What you have can work, though I'd consider it to be inelegant - every time there's a re-render, you're attaching a new beforeunload listener (and removing the previous one), no matter whether it's necessary or not. You're also not removing the listener when the component dismounts (which would be something to keep in mind if the page this is on has could exist without this component being mounted). After this component is unmounted, it'd be best for the listener with the editorWasModfied check to be removed as well.
If I understand useEffect correctly, it runs on every render which is not something I want.
Well, you're attaching it on every render currently anyway.
I know there is a second argument, but since it refers to the editorWasModified variable, I get a warning that the dependency array should contain it, which is false since I only want the useEffect once to update the window.onbeforeunload event.
The usual way to fix this lint issue is to attach a new listener whenever the state values it references changes. Using useEffect will also let you remove the listener when the component unmounts.
useEffect(() => {
const handler = () => {
if (editorWasModfied) { // perhaps fix typo: editorWasModfied -> editorWasModified
return "You have unsaved changes. Are you sure you want to leave?";
}
};
window.addEventListener('beforeunload', handler);
return () => window.removeEventListener('beforeunload', handler);
}, [editorWasModfied]);
You are correct that useEffect runs on every render (if no dependency array was specified), but so does your code without useEffect.
Here are the three different useEffect "settings":
Runs on every render:
useEffect(() => {
})
Run once:
useEffect(() => {
}, [])
Runs everytime editorWasModfied changes:
useEffect(() => {
}, [editorWasModfied])
Sometimes we only want the useEffect to run once, meaning we don't want any dependencies in our dependency array (second argument), but the linter complains about it.
WARNING Don't use the following as an easy escape from the linter warnings. You should always try to write code following linter rules!
With that said, to suppress this linter warning, you can use // eslint-disable-next-line, but only do this if you are absolutely sure that what you are doing is correct (suppressing the linter is an edge case and is not recommended unless absolutely necessary):
useEffect(() => {
// eslint-disable-next-line
}, [])

React SSR: event listeners and render are not added in server-rendered code?

I've done some work with SSR but mainly using NextJS -- I understand why the code is being rendered (first, at least) on the server (to show the user a screen quickly, to allow search engines to parse SPAs), and to some extent how it's being done (some engine on the server side will take the SPA and convert it to HTML and then sent it 'down' to the browser).
What I don't understand is what the step-by-step process of SSR entails. I've been working through a Front End Master course on intermediate React that briefly touches on Reactdomserver -- I understand the concept: convert the SPA elements to strings, and send them on to the browser. The course says that 'event listeners are added later' (I suppose once React takes over), and that render isn't available on the server. I believe I understand the second part -- I suppose the React javascript just isn't available until React is up and running -- but don't understand what the first statement regarding event listeners actually means. When I wrote old-school HTML code, loaded it to a server, and it was downloaded to a browser, complete with whatever event listeners I had, and it just worked. What is SSR doing differently than we did in the old days writing non-SPA sites? I suppose it's that the DOM isn't available on the server, so you can't add event listeners until the HTML is rendered in the browser and the DOM is built -- but as nothing is displayed until the DOM is built, why even talk about 'adding event listeners later'?
Thanks for any help you can provide!
Let's take a very contrived example React Component:
function App() {
const [content, setContent] = useState("click me!");
return <div onClick={() => setContent("you did it")}>{content}</div>
}
Now, on the server, we want to generate HTML from it. For that, we mimic useState to return the initial state, call App(), and arrive at a JSX tree that looks like:
{ type: "div", props: { onClick: () => ..., }, children: ["click me!"] }
Now we can turn that into a string of HTML easily ...
<div>click me!</div>
and send that to the client. But wait, were did the onClick go? Well, we could not add it. For sure we could (1) add an inline onclick handler, or (2) register some event listener later, but the function was instantiated on the server, we can't just serialize it and send it to the client.
<script>
getElement("App").onClick(() => {
setContent("you did it!"); // Um ... Were does it refer to?
});
</script>
Now what to do? Well, we could take the React Component again, and turn it into a piece of JS, that is intended to run on the frontend as a whole. Then we can attach that piece of JS to the HTML we sent to the client. Now we run the same code on the frontend, but this time we do have access to the DOM, we can register handlers, and we can rerender if setState gets called. Therefore App() gets called again, but from the JSX returned ...
{ type: "div", props: { onClick: () => ..., }, children: ["click me!"] }
... we can directly generate Elements now, and attach the handler:
const el = document.createElement(jsx.type);
el.addEventListener("click", jsx.onClick);
el.appendChild("click me!");
And all that is exactly what NextJS does for you.

Javascript Video loadmetadata event not being triggered on React-Redux Application

I have a React-Redux application that reads some metadata from videos.
However the code added to the loadmetadata event is never triggered.
As a workaround I have added a timer to wait 1 second before, which is a pretty bad solution and doesn't work every time.
Another thing is that I couldn't find an elegant way to integrate the video element into Redux code without having to manipulate the DOM.
The code looks like this:
videoPlayerElement = document.getElementById(`videoplayer-${videoId}`);
videoPlayerElement.addEventListener('loadedmetadata', function(e) {
const duration = videoPlayerElement.duration;
...
})
The code inside the listener is never executed.
I also have tried different ways to assign the loadmetadata event, i.e: assigning directly to videoPlayerElement.onloadmetadata still not working.
I thought it might be because of the scope of the object, so I changed it to a global just for testing... didn't help.
Any other idea about what might be causing?
If I run a simple example, like this one it works fine.
In react you should use synthetic events where possible. Example for your use case:
class MediaPlayer extends Component {
handleMetadata = event => {
const duration = event.currentTarget.duration;
// ...
}
render() {
const {src} = this.props;
return(
<video src={src} onLoadedMetadata={this.handleMetadata} />
);
}
}

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.

Categories

Resources