React-js won't recognize AmbientLightSensor - javascript

I'm trying to use the sensor's API with react and I can't seen to be able to make it work.
It gives me an error saying that AmbientLightSensor (in my case this sensor) is undefined
If I run the script outside react ( more exactly with an extension from VSCode "live server" ) it works fine ( just a html with some JS code in it ).
That's cool and all but in this case at least I want to run this script inside react and it just doesn't let me.
So far I've tried:
running this code in react as a class method called by componentDidMount and that (Simply i've put my JS code in there ^^ )
running this code with the tag hoping that maybe react isn't really using the JS that I know and that maybe running it inside html will change this ... No it didn't do the trick
So at this point I'm unsure what to even check to make this work
Here is my code, the js code I'm trying to run is inside the Did mount component
class App extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const details = document.getElementById("details");
// Feature detection
if (window.AmbientLightSensor) {
try {
const sensor = new AmbientLightSensor();
// Detect changes in the light
sensor.onreading = () => {
details.innerHTML = sensor.illuminance;
// Read the light levels in lux
// < 50 is dark room
if (sensor.illuminance < 50) {
document.body.className = "darkLight";
} else {
document.body.className = "brightLight";
}
};
// Has an error occured?
sensor.onerror = event =>
(document.getElementById("details").innerHTML =
event.error.message);
sensor.start();
} catch (err) {
details.innerHTML = err.message;
}
} else {
details.innerHTML =
"It looks like your browser doesnt support this feature";
}
}
render() {
return (
<div className="app">
<h1>Ambient Light Sensor</h1>
<p>Current Light Levels</p>
<div id="details"></div>
</div>
);
}
}
And also here is the working html
<head>
<meta charset="UTF-8" />
<title>Ambient Light Sensor</title>
</head>
<body class="brightLight">
<h1>Ambient Light Sensor</h1>
<p>Current Light Levels</p>
<div id="details"></div>
</body>
<script>
const details = document.getElementById("details");
// Feature detection
if (window.AmbientLightSensor) {
try {
const sensor = new AmbientLightSensor();
// Detect changes in the light
sensor.onreading = () => {
details.innerHTML = sensor.illuminance;
// Read the light levels in lux
// < 50 is dark room
if (sensor.illuminance < 50) {
document.body.className = "darkLight";
} else {
document.body.className = "brightLight";
}
};
// Has an error occured?
sensor.onerror = event =>
(document.getElementById("details").innerHTML =
event.error.message);
sensor.start();
} catch (err) {
details.innerHTML = err.message;
}
} else {
details.innerHTML =
"It looks like your browser doesnt support this feature";
}
</script>
</html>```
PS* for this to work you need to run this on a https server

That's not how React works...
I'd suggest looking into dangerouslySetInnerHTML link here: https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
And createRef link here: https://reactjs.org/docs/refs-and-the-dom.html#creating-refs
Here is a simple example utilizing both to give you a better idea:
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.detailsRef = React.createRef();
}
createMarkup() {
return { __html: 'whatever you want...' };
}
componentDidMount() {
console.log(this.detailsRef.current.innerHTML);
}
render() {
return (
<div className="app">
<h1>Ambient Light Sensor</h1>
<p>Current Light Levels</p>
<div
ref={this.detailsRef}
dangerouslySetInnerHTML={this.createMarkup()}
/>
</div>
);
}
}
export default App;
Play around with it and read the links from the official docs to adapt to your specific use case...

Never used the AmbientLightSensorAPI before but: try something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: ''
};
}
componentDidMount() {
if (window.AmbientLightSensor) {
try {
const sensor = new AmbientLightSensor();
sensor.onreading = () => {
this.setState({ details: sensor.illuminance });
if (sensor.illuminance < 50) {
document.body.className = 'darkLight';
} else {
document.body.className = 'brightLight';
}
};
sensor.onerror = event =>
this.setState({ details: event.error.message });
sensor.start();
} catch (err) {
this.setState({ details: err.message });
}
} else {
this.setState({
details:
'It looks like your browser doesnt support this feature'
});
}
}
render() {
return (
<div className="app">
<h1>Ambient Light Sensor</h1>
<p>Current Light Levels</p>
<div id="details">{this.state.details}</div>
</div>
);
}
}

Ok so I got it :D
The only working way I found to send a JavaScript file and run it is by declaring it in the index.html file inside the public folder ( if u made the app with create react app )
Normally react servers you this file and it adds it's components over it.
The reason why running the script inside a react class won't do ( as far as I managed to understand ) is that react isn't actually running JavaScript there but a language based on top of JavaScript ES6.
While this means that most functionalities you may be accustomed with are working in it there are exceptions too, mostly to new functionalities ( as the sensor's API, its pretty new ).
This may not be a problem in the future, but for the time being I guess this is one way to do it.
Edit * And #SakoBu's answer turned out to be the safe way of doing
this
( #Change my mind meme :3 )

Related

How to render a mermaid flowchart dynamically?

I am using the mermaid library to build flowcharts. The principle of its work is that inside a block there is a pseudocode - commands of a special syntax, on the basis of which the flowchart is built in the block.
I want to be able to change the contents of the block dynamically, and the script rebuilds the block diagram every time.
How should I set up initialization? Perhaps I should add some callback function in the settings?
I initialized in this way:
mermaid.init({/*what setting parameters should be here?*/}, ".someClass"/*selector*/);
but the script doesn’t render any new commands. It only renders the commands that existed at the moment the document was loaded.
In other words, I want to edit a flowchart online.
function edit() {
const new_mermaid = document.createElement("div");
new_mermaid.classList.add("mermaid");
new_mermaid.classList.add(".someClass");
/*new_mermaid.innerHTML =
`graph TD
1[point 1] --> 2[point 2]`;*/
// it doesn't work when I append the new element dynamically!
new_mermaid.innerHTML = document.querySelector(".mermaid").innerHTML;
// it works always.
document.body.append(new_mermaid);
/* document.querySelector(".mermaid").innerHTML =
`
graph TD
A --> B`*/
// it doesn’t work with event listener
}
edit(); // it works
document.body.addEventListener("click", edit)
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
// how to do it correctly?
mermaid.init({
noteMargin: 10
}, ".someClass");
</script>
<div class="mermaid someClass">
graph TD
1--> 2
3 --> 2
2 --> 1
</div>
It seems, I know the answer. Look at the solution below:
document.querySelector("button").addEventListener("click", (e) => {
const output = document.querySelector(".flowchart");
if (output.firstChild !== null) {
output.innerHTML = "";
}
const code = document.querySelector(" textarea").value.trim();
let insert = function (code) {
output.innerHTML = code;
};
mermaid.render("preparedScheme", code, insert);
});
<script src="https://unpkg.com/mermaid#7.1.0/dist/mermaid.min.js"></script>
<p>Input your data:</p>
<div class="input">
<textarea style="width:300px; height:200px"></textarea>
<br>
<button>render</button>
</div>
<div>
<p>output:</p>
<div class="render_container" style = "width:300px; height:200px; border:thin solid silver" >
<div class="flowchart"></div>
</div>
</div>
Thanks for the answer above. I would like to add a react wrapper to the answer scope for whoever using react:
import React, {Component} from "react";
import mermaid from "mermaid";
export default class Mermaid extends Component {
constructor(props){
super(props)
this.state={
chart: this.props.chart || ""
}
mermaid.initialize({
mermaid : {
startOnLoad: false,
}
})
this.mermaidRef = React.createRef()
}
mermaidUpdate(){
var cb = function (svgGraph) {
this.mermaidRef.current.innerHTML = svgGraph
};
//console.log("this.state.chart", this.state.chart)
mermaid.mermaidAPI.render('id0', this.state.chart, cb.bind(this));
}
componentDidMount(){
this.mermaidUpdate()
}
componentDidUpdate(prevProps, prevState) {
//console.log("Mermiad prevProps.chart", prevProps.chart)
if (this.props.chart !== prevProps.chart) {
this.setState({chart:this.props.chart},()=>{
this.mermaidUpdate()
})
}
}
render() {
var outObj = (
<div
ref={this.mermaidRef}
className="mermaid"
>
{this.state.chart}
</div>
)
return outObj
}
}

ReactJS - Change Button Text For a Specific Button on an Event

React newbie here,
EDIT: Sorry, I forgot to add the eventListener code. Then render will just output {this.state.songs}. This is separate from the code I posted above and from a modified version of the code I posted above.
import React, { Component } from 'react';
class AudioList extends Component {
constructor (props) {
super(props);
this.state = {
songs: ''
}
this.audios = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i].song_url));
}
}
componentWillMount() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener('play', () => console.log('Play'));
songs.push(
<div className='song-preview'>
<button className='preview' onClick={() => this.toggle(this.audios[i])}>Preview {i}</button>
</div>
)
}
this.setState({
songs: songs
})
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio () {
return this.audios.find(audio => false === audio.paused);
}
toggle (nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render () {
if (this.state.songs) {
return (
<div className='song-list'>
{ this.state.songs }
</div>
)
} else {
return (
<div className='song-list'></div>
)
}
}
}
export default AudioList;
I am using this code from a previous solution that I found on Stackoverflow (https://stackoverflow.com/a/50595639). I was able to implement this solution to solve my own challenge of needing to have multiple audio sources with one audio player and multiple buttons. However, I am now faced with a new challenge - I want a specific button's text to change when an event is fired up.
I was thinking about implementing state that contains the text for the button that changes when an event fires up, but that wont work because every button will be reading off of the same variable in state and re-rendering, I only want one button to re-render.
I was also thinking about having multiple variables in state that control each specific button, but given that my modified implementation has an dynamic number of audio sources, I don't know the number of state variables I will need.
Does anyone have any suggestions?
Thomas from my secondary thread was kind enough to re-factor my code and essentially re-write parts of it to fix this issue.
You can read his solution here: https://stackoverflow.com/a/62769376/8888293

How to return an HTML <div> tag from a javascript function in React?

I am working on a React application where I am trying to render text on the screen when a button is clicked. I have defined a function onButtonClick which gets triggered whenever the button is clicked. However, the HTML that I am returning from the function is not rendered on the screen. I am in the learning stages of React so please excuse me if the question seems silly.
class App extends Component {
constructor() {
super();
this.state = {
blockno:0
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1})
return(
<div>
<h3>Some text</h3>
</div>
);
}
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
</div>
);
}
}
The value is being returned, but the framework/browser/etc. has no reason to do anything with that value.
Try thinking about this a different way, a "more React way". You don't want to return the value to be rendered, you want to update state. Something like this:
constructor() {
super();
this.state = {
blockno:0,
showDiv: false // <-- note the new property in state
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1, showDiv: true})
}
Now you're not returning anything, but rather updating the state of the component. Then in your render method you conditionally render the UI based on the current state:
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
{
this.state.showDiv
?
<div>
<h3>Some text</h3>
</div>
: ''
}
</div>
);
}
The click handler doesn't modify the page, it just modifies the state of the component you're writing. The render method is responsible for rendering the UI based on that state. Any time state changes, render will be called again to re-render the output.
(Note: It's not 100% clear if this is exactly the functionality you're looking for in the UI, since it's not really clear what you're trying to build. But the point here is to illustrate how to update state and render output in React. Your logic can be tweaked as needed from there.)
You have to make a render based on your state. Please check the tutorial at the react docs to learn more about how React works. It's really good
Here is a version of your code that works. Hope it helps
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
};
}
OnButtonClick = () => {
//updates the states
this.setState({ blockno: this.state.blockno + 1 });
};
//remember: every time there is an update to the state the render functions re-runs
render() {
//variable holding the blocks in an array
let blocks = []
//if blockno is greater than 0, it checks everytime that there is a state change
if (this.state.blockno > 0) {
//for every block added
for (let index = 0; index < this.state.blockno; index++) {
//We`re going to add to the array of blocks a new div with the block number
blocks.push(
<div>
<h3>My block number is {index}</h3>
</div>
);
}
}
return (
<div>
<div>
{/**button that updates the state on every click */}
<button onClick={this.OnButtonClick}>
Click me to add a new div!
</button>
</div>
{/**This render the blocks variable that holds the divs */}
{blocks}
</div>
);
}
}
What I see is that you are trying to build a counter. The value that you're returning from the click handler function can't be rendered, instead you need to manage it in the render function as follow:
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
}
}
OnButtonClick = () => {
this.setState(prevState => ({ blockno: prevState.blockno + 1 }));
}
render() {
return(
<div>
{this.state.blockno > 0 && <div>some text {this.state.blockno}</div>}
<Button onButtonClick={this.OnButtonClick} />
</div>
);
}
}
Also note that the setState method is asynchronous, please read the documentation https://reactjs.org/docs/react-component.html#setstate

React updating DOM in an unpredictable manner with setState and class names

I'm attempting to do an animation with React and CSS classes. I have created a live demo, if you visit it and click the Start button you will see the text fade in and up one by one. This is the desired animation that I am after.
However, there seems to be issues of consistency when you hit Start multiple times and I cannot pinpoint why.
The Issue: Below is a recording of the issue, you can see the number 1 is not behaving as expected.
live demo
The process: Clicking Start will cancel any previous requestAnimationFrame' and will reset the state to it's initial form. It then calls the showSegments() function with a clean state that has no classNames attached to it.
This function then maps through the state adding a isActive to each segment in the state. We then render out the dom with a map and apply the new state.
This should create a smooth segmented animation as each class gets dropped one by one. However when i test this in Chrome (Version 56.0.2924.87 (64-bit)) and also on iOS, it is very inconsistent, sometimes it works perfectly, other times the first DOM element won't animate, it will just stay in up and visible it's completed transitioned state with "isActive".
I tried to replicate this issue in safari but it worked perfectly fine, I'm quite new to react so i am not sure if this is the best way to go about things, hopefully someone can offer some insight as to why this is behaving quite erratic!
/* MotionText.js */
import React, { Component } from 'react';
import shortid from 'shortid';
class MotionText extends Component {
constructor(props) {
super(props);
this.showSegments = this.showSegments.bind(this);
this.handleClickStart = this.handleClickStart.bind(this);
this.handleClickStop = this.handleClickStop.bind(this);
this.initialState = () => { return {
curIndex: 0,
textSegments: [
...'123456789123456789123456789123456789'
].map(segment => ({
segment,
id: shortid.generate(),
className: null
}))
}};
this.state = this.initialState();
}
handleClickStop() {
cancelAnimationFrame(this.rafId);
}
handleClickStart(){
cancelAnimationFrame(this.rafId);
this.setState(this.initialState(), () => {
this.rafId = requestAnimationFrame(this.showSegments);
});
}
showSegments() {
this.rafId = requestAnimationFrame(this.showSegments);
const newState = Object.assign({}, this.state);
newState.textSegments[this.state.curIndex].className = 'isActive';
this.setState(
{
...newState,
curIndex: this.state.curIndex + 1
},
() => {
if (this.state.curIndex >= this.state.textSegments.length) {
cancelAnimationFrame(this.rafId);
}
}
);
}
render(){
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
}
export default MotionText;
Thank you for your time, If there any questions please ask
WebpackBin Demo
Changing the method to something like this works
render(){
let d = new Date();
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={d.getMilliseconds() + obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
How this helps is that, the key becomes different than previously assigned key to first span being rendered. Any way by which you can make the key different than previous will help you have this animation. Otherwise React will not render it again and hence you will never see this in animation.

React/webpack conditionally return require.ensure component (code splitting)

I have a sub component that does not need to be loaded immediately that I want to split out. I am trying to conditionally load in a react component via require.ensure. I am not getting any console errors but I am also not seeing anything being loaded. Here is the code I am calling :
renderContentzones() {
if (this.props.display ) {
return require.ensure([], () => {
const Component = require('./content-zones/component.jsx').default;
return (
<Component
content={this.props.display}
/>
);
});
}
return null;
}
It is just rendering a blank screen currently (no errors). This previously worked when I used import 'displayComponent' from './content-zones/component.jsx' and just returned it like you normally would in react, instead of this require.ensure but. Not sure what I am doing wrong here, any idea how to make something like this work? Thanks!
This is one way to do it, using the state to show the dynamic loaded component:
constructor(){
this.state = {cmp:null};
}
addComponent() {
const ctx = this;
require.ensure(['../ZonesComponent'], function (require) {
const ZonesComponent = require('../ZonesComponent').default;
ctx.setState({cmp:<ZonesComponent />});
});
}
render(){
return (
<div>
<div>Some info</div>
<div><button onClick={this.addComponent.bind(this)}>Add</button></div>
<div>
{this.state.cmp}
</div>
</div>
);
}
When you press the button add the component will be shown.
Hope this help.

Categories