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} />
);
}
}
Related
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
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.
Most examples for using RxJS to observe button clicks are like:
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.subscribe(() => console.log('Clicked!'));
Is this okay in React? Since there is the virtual DOM, is it still okay to get reference to the real DOM like this? What happens after a re-render? Is the subscription lost?
I.e. they use document.querySelector.
But when I write my render() method, I'm used to <button onClick={...} >.
What is the recommended way to do this in React? Get rid of the onClick inside the JSX, add a class/id to my button, or maybe a ref, and use the style above?
Also, should I unsubscribe anywhere, e.g. in componentWillUnmount()? In which case I'd want to keep references to all my subscribers (event listeners)? In which case this seem much more complex than the old (callback) way.
Any suggestions, thoughts on this?
I've thought about this a lot - I think the answer by #ArtemDevAkk is one really good way to do it, but I'm not sure it accounts for how you intend to use the data. I'd like to suggest an alternative approach. Admittedly, I've used hooks for this, but you could do this in old school classes in a similar way.
I've had to break up your question into a few parts:
How to reference a DOM node in React
How to create a Subject and make it fire
How to subscribe to any Observable in React (useEffect)
Getting a reference to a DOM node
In case all you are asking is how to reference a DOM node, the rule of thumb is that to access the DOM in React, you use the useRef hook (or createRef in a Class).
Keeping your Subject active (for sharing events)
The benefit of this method is that your Subject will be created once and kept alive indefinitely, so anything is able to subscribe and unsubscribe at will. I can't think of a great reason for using RxJs in a React project because React has its own ways to handle events, so it's hard to know if this will solve your problem.
function MyRxJsComponent() {
// establish a stateful subject, so it lives as long as your component does
const [ clicks$ ] = useState(new Subject());
// register an event handler on your button click that calls next on your Subject
return <button onClick={e => clicks$.next(e)}></button>
}
This will create one Subject that will stay alive as long as your component is alive on the page. My best guess for how you might use this is by using a Context to contain your subject (put clicks$ in your context) and then pushing events to that Subject.
Note how I'm not using fromEvent to create an Observable, because this Observable would be created and destroyed with your button, making it impossible for anything to stay subscribed to it outside the component.
Just using an Observable internally
Alternatively (as #ArtemDevAkk alluded to), you could just create your Observable for local use and accept the limitation that it will be created and destroyed (this might actually be preferable, but again, it depends on what you're actually trying to achieve).
function MyRxJsComponent() {
// get a reference to the button (use <button ref={buttonRef} in your component)
const buttonRef = useRef(null)
// use useEffect to tell React that something outside of its
// control is going to happen (note how the return is _another_
// function that unsubscribes.
useEffect( () => {
// create the observable from the buttonRef we created
const clicks$ = fromEvent(buttonRef.current, 'click')
clicks$.subscribe( click => {
// do whatever you need to with the click event
console.log('button was clicked!', click)
})
return () => {
start$.unsubscribe()
}
}, [buttonRef]);
return <button ref={buttonRef}>Click me!</button>;
}
But should you?
I think overall, I've shown above how you can use state to keep a Subject alive for as long as you need; you can use useEffect() to subscribe to an observable and unsubscribe when you don't need it any more. (I haven't tested the samples locally, so there might be a small tweak needed..)
I mentioned using a Context to share your Subject with other components, which is one way I can imagine RxJs being useful in React.
Ultimately, though, if this is all you want to do, there are better ways to handle events in React. A simple <button onClick={() => console.log('clicked')}> could be all you need. React, being a reactive library in itself, isn't really designed to contain the amount of state that an Observable can contain - your views are meant to be simplified projections of state stored elsewhere. For the same token, it's generally advised that you don't try to reference a specific element unless you have an exceptional reason to do so.
const buttonRef = useRef(null)
useEffect( () => {
const start$ = fromEvent(buttonRef.current, 'click').subscribe( click => {
console.log('click event :', click)
})
return () => {
start$.unsubscribe()
}
}, [])
add ref to button
This has happened to me a few times. I have always managed to work around the issue, yet I am still intrigued to understand why this happens, and what I have missed.
Essentially if I have a condition within my render method which specifies the class for my div:
let divClass = this.state.renderCondition ? 'red' : 'blue';
By default I set renderCondition within my state to false.
If I then define an onClick handler on a button (as follows), and click the button, whilst render IS called, the DOM is NOT updated. That is to say the class does not change.
onClickCompile: function() {
this.setState({renderCondition: true}, function() {
synchronousSlowFunction();
});
}
This seems to have something to do with running slow synchronous code in that if the code is quick and simple the DOM IS updated appropriately.
If I wrap the call to synchronousSlowFunction in a 500 millisecond timeout, everything works as expected. I would however like to understand what I have misunderstood such that I do not need this hack.
Can you share the button onClick code? I might be wrong but this looks like an incorrectly set button onClick listener.
Make sure that onClick callback is defined without (), i.e.
<button onClick={this.something} />
instead of:
<button onClick={this.something()} />
Post more code so we can get a better (bigger) picture
synchronousSlowFunction is as you mentioned synchronous. This means, that it blocks your component while it is running. That means, that react cannot update your component, because it has to wait for the callback function to complete until it can call render with the updated values. The setTimeout wrap makes the call asynchronous, so that react can render/update your component, while the function is doing its work. It is not the time delay, that makes it work but simply the callback, which is not render blocking. You could also wrap in in a Promise or make the slow function async to prevent render blocking.
Try something Like this:
this.setState((state) => ({
...state, renderCondition: true
}));
Maybe you're doing another setState for renderCondition some where the code above should fix such things.
or maybe try using PureComponent
import React, { PureComponent } from 'react'
export default class TablePreferencesModal extends PureComponent {
render() {
return (
<div>
</div>
)
}
}
After set state use this line
this.setState({})
I have a question regarding a scenario I keep running into building HTML5 games resulting in difficult to manage circular dependencies.
I understand completely why the circular dependency is occuring and where it is occurring. However, I can't seem to figure out a convenient way to get around it, so I assume my logic / approach is fundamentally flawed.
Here's a little bit of context.
I have a game that has a single point of entry (compiled with Webpack) called Game.js. I have a basic event manager that allows for two functions on(key, callback) and fire(key, parameters).
The event manager simply creates an object, sets the supplied key of on as a property with an array value populated with any callback functions registered to that key. When the fire method is called that property is retrieved and all of the fuctions defined in it's array value are invoked.
What I'm trying to do
I want to be able to instance the event manager on Game.js and export an instance of Game that other classes can import and subsequently register callbacks to the Game instances event manager.
class Game {
constructor() {
this.events = new EventManager();
window.addEventListener('resize', this.resize.bind(this));
}
resize(event) {
if(window.innerWidth < window.innerHeight) {
this.events.fire('orientation-change', 'vertical');
} else {
this.events.fire('orientation-change', 'horizontal');
}
}
}
export default new Game();
Then for example a Button class may need to respond to an orientation change event fired by the Game. Please note the above is simply an example of a circumstance in which the event manager may fire an event, but this condition could be anything.
import Game from '../core/Game';
class Button {
constructor() {
Game.events.on('orientation-change', this.reorient.bind(this));
}
reorient() {
// ...
}
}
export default Button;
The above class is a UI component called Button that needs to know when the orientation-change event is fired, again please note this event could be anything.
What's the problem?
Nothing looks particularly wrong with the above, however, because Game.js is the entry point, at some point an instance of Button is created whether it be directly in Game.js or through another class which is subsequently instanced via Game.js which of course causes a circular dependency because even if not directly, Game imports Button and Button imports Game.
What I've tried
There are two main solutions that I have found that work (to some degree). The first being simply waiting for the export to be available using an interval check of the value of Game in the constructor of Button, like this:
import Game from '../core/Game';
class Button {
constructor() {
let check = setInterval(() => {
if(Game !== undefined) {
Game.events.on('orientation-change', this.reorient.bind(this));
clearInterval(check);
}
}, 100);
}
reorient() {
// ...
}
}
export default Button;
This will typically resolve in a single iteration.
The second solution being to use dependency injection and pass reference of Game to Button when it's instanced, which again works great, but the prospect of having to repeatedly do this per class seems unintuitive. The interval check works fine too, but seems hacky.
I'm feel like I'm completely missing something and that the solution isn't a difficult as I'm making it.
Thanks for any help regarding this.