Im trying to write a custom camera component inside my ReactJS component because i cant seem to find any npm library that really fits my requirement. So i found this article https://frontendnews.io/editions/2018-08-15-simple-camera-component that gives a pretty good details. But i think im having trouble in changing these pure javascript code into React component. I cant seem to render the Camera component inside my page. Plase take a look at my code and see what i did wrong.
Here's my Camera component:
import {Component} from 'react'
class Camera extends Component {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });
this.videoElement = document.createElement('video');
this.canvasElement = document.createElemnt('canvas');
this.videoElement.setAttribute('playsinline', true);
this.canvasElement.style.display = 'none';
shadow.appendChild(this.videoElement);
shadow.appendChild(this.canvasElement);
}
open(constraints) {
return navigator.mediaDevices.getUserMedia(constraints)
.then((mediaStream) => {
// Assign the MediaStream!
this.videoElement.srcObject = mediaStream;
// Play the stream when loaded!
this.videoElement.onloadedmetadata = (e) => {
this.videoElement.play();
};
});
}
}
export default Camera
Here's my cam.js where i render the camera component:
import React, { Component } from 'react';
import Camera from '../elements/camera';
import ElemLayout from '../elements/layout';
class App extends Component {
constructor(props){
super(props)
this.state = {
isLoading: true
}
}
componentDidMount(){
this.setState({ isLoading: false })
}
async function() {
const camera = document.querySelector('Camera');
camera != null && await camera.open({ video: { facingMode: 'user' }})
}
render () {
return (
<ElemLayout routes={ this.props.routes } isLoading={this.state.isLoading} >
<Camera />
</ElemLayout>
);
}
}
export default App;
First thing I notice, you're extending React.Component, but you're missing a call to render()
When you use a Class component, a call to the render() lifecycle method is REQUIRED. Here is some more info on React Components:
React.Component
When I click the link to the article, I notice that they're making use of web components. You actually don't really need to make any changes to that tutorial's code.
You can use web components in React, see this page in the React docs:
React & Web Components
Related
I have a class inheriting React.Component as such :
import { Component } from "react"
import {Map} from "ol" // the goal is to render a map with openlayers
import './MapCanvas.css'
class MapCanvas extends Component {
constructor(props) {
super(props)
this.state = {}
}
componentDidMount() {
console.log('hello') // called twice
const canvas = new Map(/* map options, irrelevant here */)
this.setState({canvas})
}
componentWillUnMount () {
console.log('goodbye') // never called
if (this.state && this.state.canvas) {
this.state.canvas.setTarget(undefined) // should remove the map
}
}
render() {
return (
<div id="map" className="map"></div>
)
}
}
export default MapCanvas
My app looks like this
import MapCanvas from './components/MapCanvas'
import './App.css'
function App() {
return (
<MapCanvas/>
)
}
export default App
and index is
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './views/App';
const root = createRoot(document.getElementById('app'))
root.render(
<StrictMode>
<App/>
</StrictMode>
)
If I remove StrictMode, Everything looks fine,
However if I leave it, the componentDidMount method of MapCanvas gets called twice, and I end up having 2 maps on the page. componentWillUnmount never gets called in the process.
I read here
and there
that React StrictMode calls some functions multiple times in Developpement mode.
As far as I understood, it is to help us code in a cleaner way, but I can't understand what I am supposed to do.
My question is, where am I supposed to setup the ol.Map so that it respects the best practices and gets called only once, OR gets properly "destroyed" on update ?
edit
just as posting the question I realized I had a typo on componentWillUnmount (M instead of m)
I Corrected it, now I see "goodbye" in the console, but I still have 2 maps.
If this question is more about openlayer thant about react, let it know in the comments and I'll update or delete it
edit #2
Using class attributes instead of React state to store the canvas gives the expected result.
Is this considered good practice or is there a better way ?
Here is a workaround that worked for me:
using a class attribute instead of the React State
import { Component, createRef } from "react"
import { Map } from "ol"
class MapCanvas extends Component {
constructor(props) {
super(props)
this.canvas = new Map(/* options */)
this.ref = createRef()
}
componentDidMount() {
this.canvas.setTarget(this.ref.current)
}
componentWillUnmount () {
this.canvas.setTarget(undefined)
}
render() {
return (
<div ref={this.ref} className="map"></div>
)
}
}
export default MapCanvas
It may not be good react practice, but it solves the prolem for now
If you have a better solution you can post an answer i'll consider accepting it instead
How to fetch videos from VdoCipher and display then on my React js WebApp?
I am currently trying to use VdoCipher to store videos (I will upload them manually on the website) and then display them on my react webapp... infortuntly the documentation isn't very clear for me.
Here is some sample code to implement this
import React, { Component } from "react";
import "./style.css";
class VideoPlayer extends Component {
constructor(props) {
super(props);
this.state = {
videoObject: null
};
}
componentDidMount() {
if (window.VdoPlayer) {
return this.loadPlayer();
}
const playerScript = document.createElement("script");
playerScript.src =
"https://player.vdocipher.com/playerAssets/1.6.10/vdo.js";
document.body.appendChild(playerScript);
playerScript.addEventListener("load", () => {
return this.loadPlayer();
});
}
loadPlayer() {
window.playerContainer = this.refs.container;
new window.VdoPlayer({
otp: this.props.otp,
playbackInfo: this.props.playbackInfo,
theme: "9ae8bbe8dd964ddc9bdb932cca1cb59a",
container: this.refs.container
});
}
render() {
return <div className="player" ref="container"></div>;
}
}
export default VideoPlayer;
Codesandbox link for working version
Since there is a javascript src to be loaded, it checks in componentDidMount for the presence of global variable. You can choose to use this by alternative means such as scriptjs or adding script tag the index.html template.
The return of the new VdoPlayer() is supposed to be the reference of player that you need to call javascript APIs on player. If needed, you can make it available to the outside components with a callback prop. And then call this callback prop after the new VdoPlayer()
I have the package with free and pro components.
I need to add new functionality (pro) for exists free component.
For example, I have a free component
import React from "react";
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
clickCounter: 0
}
}
addClick = () => {
this.setState({
clickCounter: this.state.clickCounter+1
})
}
render() {
return (
<button onClick={this.addClick}>Click {this.state.clickCounter}</button>
);
}
}
export default Example;
and in folder component/pro/ I want new functionality. (This folder is being deleted for the free package build)
And I would like the same component to have more possibilities under the same name for the pro version only.
For example, the limit of clicks.
What is the best way to do it?
Should I remove excess code from the component, or somehow can I load additional functions into an existing component.
If I understood the question correctly, you are distinguishing between a free and a Pro user, and based upon that you want to serve the respective features.
Your user details API should tell you about the user status, (Free or PRO). Based upon that response you can implement like below.
import FreeExample from './FreeExample';
import ProExample from './ProExample';
import AppLoader from './AppLoader';
class App extends React.Component {
isProUser = ({ userData }) => userData.status === 'PRO';
render() {
const { userData, isLoading } = this.props;
if(isLoading) return <AppLoader />
return (
{ isProUser({ userData }) ? <ProExample /> : <FreeExample />}
);
}
}
I have a server side rendered audio player react app.
Using NextJS' custom _app.js, I have a persistent AudioPlayer component, in which I want to call an internal this.play(mp3url) function whenever the #observable playerData = {} changes in the mobx store.
I have successfully made button clicks on other components in my app update playerData whenever required.
But I am unable to detect these updates inside my AudioPlayer component, so that I can set its state and call this.play(mp3url) to start playing the audio.
pages/_app.js
import initializeStore from '../stores/stores';
export default class MyApp extends App {
static async getInitialProps(appContext) {
const mobxStore = initializeStore();
appContext.ctx.mobxStore = mobxStore;
const appProps = await App.getInitialProps(appContext);
return {
...appProps,
initialMobxState: mobxStore,
};
}
constructor(props) {
super(props);
this.mobxStore = props.initialMobxState;
}
render () {
const { Component, pageProps } = this.props
return (
<Provider {...this.mobxStore}>
<Component {...pageProps} />
{ this.mobxStore.playerStore.playerActive && <AudioPlayer /> }
</Provider>
)
}
}
components/AudioPlayer.js
import ReactPlayer from 'react-player'
import { inject, observer } from 'mobx-react'
#inject('playerStore')
#observer
class Player extends Component{
constructor(props) {
super(props)
this.state = {
url: null,
image: null
}
}
componentWillUpdate(nextProps) {
this.setState({
url: nextProps.playerStore.playerData.url,
image: nextProps.playerStore.playerData.image
}, () => { this.play(this.state.url) })
}
// ...Other react-player functions
render() {
<ReactPlayer
{/* ...required props... */}
/>
}
}
So how would one get this persistent audio player to detect changes in the
mobx observable to play audio urls?
As of now, componentWillReceiveProps, componentWillUpdate or componentDidUpdate do not get called. componentDidMount gets called exactly once as expected when I set #observable playerActive = true
Managed to figure it out. It was caused by two issues.
As per the Mobx docs
The observer function / decorator can be used to turn ReactJS components into reactive components. It wraps the component's render function in mobx.autorun to make sure that any data that is used during the rendering of a component forces a re-rendering upon change. It is available through the separate mobx-react package.
A store observable needs to be used in the render function of the observer component for the component to react to its changes. So I added them in the AudioPlayer Component instead of taking it from its own state.
I was missing this crucial line of code in the pages/_app.js constructor that would have initialised the mobx store correctly on the client side.
this.mobxStore = isServer ? props.initialMobxState : initializeStore(props.initialMobxState)
Now the AudioPlayer component reacts to each update to the mobx observable and I am able to handle any further needed logic in componentWillUpdate/componentDidUpdate
I've tried using componentWillMount and componentDidMount to initialize CKEditor from within the context of React, but it doesn't seem to work no matter what combination I try. Has anyone found a solution to this besides switching editors?
I published a package on Npm for using CKEditor with React. It takes just 1 line of code to integrate in your project.
Github link - https://github.com/codeslayer1/react-ckeditor.
How to Use?
Install the package using npm install react-ckeditor-component --save.
Then include the component in your React app and pass it your content and any other props that you need(all props listed on Github page) -
<CKEditor activeClass="editor" content={this.state.content} onChange={this.updateContent} />
The package uses the default build of CKEditor but you can use a custom build as well along with any of the plugins you like. It also includes a sample application. Hope you will find it useful.
Sage describes an awesome solution in his answer. It was a lifesaver, as I've only just started using React, and I needed it to get this going. I did, however, change the implementation, also incorporating Jared's suggestions (using componentDidMount). Also, my need was to have a change callback, like so:
Usage of the component:
<CKEditor value={this.props.value} onChange={this.onChange}/>
Added this to index.html:
<script src="//cdn.ckeditor.com/4.6.1/basic/ckeditor.js"></script>
Using the following component code:
import React, {Component} from "react";
export default class CKEditor extends Component {
constructor(props) {
super(props);
this.componentDidMount = this.componentDidMount.bind(this);
}
render() {
return (
<textarea name="editor" cols="100" rows="6" defaultValue={this.props.value}></textarea>
)
}
componentDidMount() {
let configuration = {
toolbar: "Basic"
};
CKEDITOR.replace("editor", configuration);
CKEDITOR.instances.editor.on('change', function () {
let data = CKEDITOR.instances.editor.getData();
this.props.onChange(data);
}.bind(this));
}
}
Again, all credits to Sage!
The following is an improved version of the basic version above, which supports multiple CKEditor instances on the same page:
import React, {Component} from "react";
export default class CKEditor extends Component {
constructor(props) {
super(props);
this.elementName = "editor_" + this.props.id;
this.componentDidMount = this.componentDidMount.bind(this);
}
render() {
return (
<textarea name={this.elementName} defaultValue={this.props.value}></textarea>
)
}
componentDidMount() {
let configuration = {
toolbar: "Basic"
};
CKEDITOR.replace(this.elementName, configuration);
CKEDITOR.instances[this.elementName].on("change", function () {
let data = CKEDITOR.instances[this.elementName].getData();
this.props.onChange(data);
}.bind(this));
}
}
Please note that this requires some unique ID to be passed along as well:
<CKEditor id={...} value={this.props.value} onChange={this.onChange}/>
This is for a React component which displays a P paragraph of text. If the user wants to edit the text in the paragraph, they can click it which will then attach a CKEditor instance. When the user is done altering the text in the Editor instance, the "blur" event fires which transfers the CKEditor data to a state property and destroys the CKEditor Instance.
import React, {PropTypes, Component} from 'react';
export default class ConditionalWYSIWYG extends Component {
constructor(props) {
super(props);
this.state = {
field_name:this.props.field_name,
field_value:this.props.field_value,
showWYSIWYG:false
};
this.beginEdit = this.beginEdit.bind(this);
this.initEditor = this.initEditor.bind(this);
}
render() {
if ( this.state.showWYSIWYG ) {
var field = this.state.field_name;
this.initEditor(field);
return (
<textarea name='editor' cols="100" rows="6" defaultValue={unescape(this.state.field_value)}></textarea>
)
} else {
return (
<p className='description_field' onClick={this.beginEdit}>{unescape(this.state.field_value)}</p>
)
}
}
beginEdit() {
this.setState({showWYSIWYG:true})
}
initEditor(field) {
var self = this;
function toggle() {
CKEDITOR.replace("editor", { toolbar: "Basic", width: 870, height: 150 });
CKEDITOR.instances.editor.on('blur', function() {
let data = CKEDITOR.instances.editor.getData();
self.setState({
field_value:escape(data),
showWYSIWYG:false
});
self.value = data;
CKEDITOR.instances.editor.destroy();
});
}
window.setTimeout(toggle, 100);
}
}
The self.value = data allows me to retrieve the text from the parent component via a simple ref
The window.setTimeout(); gives React time to do what it does. Without this delay, I would get an Cannot read property 'getEditor' of undefined error in the console.
Hope this helps
Just refer the ckeditor.js in index.html, and use it with window.CKEDITOR. Don't use CKEDITOR straight like the document in React component.
Just read the first-line of ckeditor.js, you will find what about define of CKEDITOR.
Thanks to Sage, Sander & co. I just wanted to contribute a version for the "inline" mode of CKEditor.
First, disable CKEditor's "auto-inline" behavior with...
CKEDITOR.disableAutoInline = true
Then, for the actual component...
import React, {Component} from 'react';
export default class CKEditor extends Component {
constructor(props) {
super(props);
this.elementName = "editor_" + this.props.id;
this.componentDidMount = this.componentDidMount.bind(this);
this.onInput = this.onInput.bind(this);
}
onInput(data) {
console.log('onInput: ' + data);
}
render() {
return (
<div
contentEditable={true}
suppressContentEditableWarning
className="rte"
id={this.elementName}>
{this.props.value}</div>
)
}
componentDidMount() {
let configuration = {
toolbar: "Basic"
};
CKEDITOR.inline(this.elementName, configuration);
CKEDITOR.instances[this.elementName].on("change", function() {
let data = CKEDITOR.instances[this.elementName].getData();
this.onInput(data);
}.bind(this));
}
}
Usage would be something like this:
<CKEditor id="102" value="something" onInput={this.onInput} />