I'm trying to load an image asynchronously and only when it's been loaded, display it in a React app.
componentDidMount() {
const img = new Image();
img.onload = () => {
this.setState({
originalImage: img,
});
}
img.src = './images/testImage.jpg'
}
render() {
return (
<main>
{
this.state.originalImage
}
</main>
);
}
I'm getting below error:
Objects are not valid as a React child (found: [object HTMLImageElement])
I would like to know why this error is happening. Of course if I just add an <img> tag, it works fine.
React cannot directly display HTML Element. If you want to create elements programmatically you have to use functions provided by React.
componentDidMount() {
const url = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRYd74A26KhImI1w9ZBB-KvWUcasVnQRe_UOrdZPqI4GOIN0mC3EA';
let img = React.createElement(
"img",
{
src: url,
},
)
this.setState({
originalImage: img,
})
let divExample = React.createElement(
'div',
null,
`Hello World`
);
this.setState({
divExample: divExample
})
}
render() {
return (
<div>
<div>Hi</div>
{
this.state.divExample
}
<main>
{
this.state.originalImage
}
</main>
</div>
);
}
React parses jsx element like div, img, HelloWorld (custom) etc. and create React Elements out of it.
As the error says, this.state.originalImage is an object. You probably are looking for it's src prop, which you can use like so:
render() {
return (
<main>
<img src={this.state.originalImage.src}/>
</main>
);
}
another idea is dangerouslysetInnerHTML - something like this ?
<div dangerouslySetInnerHTML={{ __html: this.state.originalImage.outerHTML }}/>}
Related
I have an page but it's heavy, and react still spend some seconds to load all components, i would like to put an div with greater z-index to overlap it. The problem:
componentWillMount prints 'test' on console, but do not render the div:
componentWillMount() {
return (
<div className={this.props.classes.modalLoading}>
TESTE
</div>
)
}
note css= 100vw, 100vh, bg: black, color: white
It's possible dismember in another component 'Loader' to use in another places? (console log don't work)
render() {
const { classes } = this.props
return (
<div className={classes.root} ref={'oi'}>
<LayoutCard {...this.props}/>
</div>
)
}
componentDidMount() {
{console.log('teste 3')}
}
Well, that is not how react works :)
It renders the JSX that is returned from the render() method.. the return value of the componentWillMount() is not associated with the render method.
If you want a loader you should set a state on the main component to swap between a return, that returns a loader div and the return that returns your page content. I'm not sure what you mean by 'loading' in react. Maybe any sync ajax stuff? Set a state after it finished.
if(this.state.loaded) {
return <Loader />
} else {
return <Content />
}
If you mean things like fonts, stylesheets and images...
well thats its a duplicate of
Show loading icon before first react app initialization
almost that, thx for the point #FelixGaebler
componentWillMount() {
this.setState({
flag: true,
})
}
render() {
const { classes } = this.props
if (this.state.flag) {
return (
<div className={this.props.classes.modalLoading}>
<span>TEST</span>
</div>
)
}
return (
<div className={classes.root}>
<LayoutCard {...this.props}/>
</div>
)
}
componentDidMount() {
this.setState({
flag: false,
})
}
I'm new to React and I am building an app that takes screen grabs from a MediaStream. Based on what I've seen, the best way to do that is to draw it onto a canvas element using the context.drawImage() method, passing in the HTMLVideoElement as an argument. Here's what my action creator looks like:
const RecordImage = function(video, canvas, encoder) {
if(!encoder) {
encoder = new GifReadWrite.Encoder()
encoder.setRepeat(0)
encoder.setDelay(100)
encoder.start()
}
const context = canvas.getContext('2d')
context.drawImage(video, 0, 0)
encoder.addFrame(context)
return {
type: RECORD_IMAGE,
payload: encoder
}
}
This worked in the past because the RecordImage action was being called from the same component that housed the <video /> and <canvas /> element, and I could pass them in like so:
takePic(event) {
event.preventDefault()
this.props.RecordImage(this.video, this.canvas, this.props.encoder)
}
...
render() {
return (
<div>
<video
ref = { video => this.video = video }
width = { this.props.constraints.video.width }
height = { this.props.constraints.video.height }
autoPlay = "true"
/>
<canvas
ref = { canvas => this.canvas = canvas }
width = { this.props.constraints.video.width }
height = { this.props.constraints.video.height }
/>
<button onClick = { this.takePic }>Take Picture</button>
</div>
)
}
However, I would like to house the "Take Picture" button in a different component. This is a problem because now I don't know how to access the <video /> and <canvas /> elements from a sibling component. Normally I would store the arguments I need as part of the state, but the drawImage() method needs to use the HTML elements themselves. I've heard it's a bad idea to store DOM elements in the state, so what would be the best way to go about this?
In React it is possible to directly call a function on one of your components.
You could add a 'getPicture' function in your video component which returns the video element?
Your video component:
getPicture() {
return this.video
}
...
render() {
return (
<div>
<video
ref = { video => this.video = video }
width = { this.props.constraints.video.width }
height = { this.props.constraints.video.height }
autoPlay = "true"
/>
</div>
)
}
The picture button component could look something like this:
takePic() {
const element = this.props.getPic
const encoder = new GifReadWrite.Encoder()
encoder.setRepeat(0)
encoder.setDelay(100)
encoder.start()
const canvas = document.createElement("canvas")
canvas.width = element.offsetWidth
canvas.height = element.offsetHeight
canvas.drawImage(video, 0, 0)
encoder.addFrame(context)
return {
type: RECORD_IMAGE,
payload: encoder
}
}
...
render() {
return (
<button onClick = { () => this.takePic() }>Take Picture</button>
)
}
And then a parent component to bind the button and the video component:
getPicture() {
return this.video.getPicture()
}
...
render() {
return (
<div>
<VideoElement ref="video => this.video = video"/>
<TakePictureButton getPic="() => this.getPicture()" />
</div>
)
}
Disclaimer: I'm not sure how the draw function works, but you get the idea
Many thanks to #stefan-van-de-vooren for setting me on the right path! My situation was complicated by the child components using Redux connect, so getting them to work required some additional setup.
First, set up the parent component to call one child from the other:
setMedia(pic) {
return this.control.getWrappedInstance().setMedia(pic)
}
getPicture() {
return this.display.getWrappedInstance().getPicture()
}
componentDidMount() {
this.setMedia( this.getPicture() )
}
render() {
return (
<div>
<DisplayContainer ref= { display => this.display = display } />
<ControlContainer ref= { control => this.control = control } />
</div>
)
}
Because Redux connect returns a higher order component, we need to use getWrappedInstance() to gain access to the child functions. Next, we need to enable getWrappedInstance() in our child components by telling connect to use refs. Also, we will set up our child functions.
DisplayContainer:
getPicture() {
return this.video
}
...
render() {
return ( <video ref= { video => this.video = video } /> )
}
export default connect(mapStateToProps, null, null, { withRef: true })(Display)
ControlContainer:
setMedia(pic) {
this.setState({ media: pic })
}
takePic(event) {
event.preventDefault()
this.props.RecordImage(this.state.media, this.canvas, this.props.encoder)
}
...
render() {
return(
<button onClick= { this.takePic }>Take Picture</button>
<canvas ref= { canvas => this.canvas = canvas } />
)
}
export default connect(mapStateToProps, mapDispatchToProps, null, { withRef: true })(Control)
The action creator remains the same:
const RecordImage = function(video, canvas, encoder) {
if(!encoder) {
encoder = new GifReadWrite.Encoder()
encoder.setRepeat(0)
encoder.setDelay(100)
encoder.start()
}
const context = canvas.getContext('2d')
context.drawImage(video, 0, 0)
encoder.addFrame(context)
return {
type: RECORD_IMAGE,
payload: encoder
}
}
I should note, we are using refs quite a bit here. It is necessary in this case because the context.drawImage() needs the actual <video /> element in order to work. However refs are sometimes considered an anti-pattern, and it is worth considering if there is a better approach. Read this article for more information: https://reactjs.org/docs/refs-and-the-dom.html#dont-overuse-refs
A better solution would be to grab the image directly from the MediaStream. There is an experimental grabFrame() method that does just that, but as of Feb 2018, there is limited browser support. https://prod.mdn.moz.works/en-US/docs/Web/API/ImageCapture/grabFrame
I wonder why following snippet is not updating DOM
const { hyper, wire } = hyperHTML;
class Input extends hyper.Component {
get defaultState() {
return { error: false };
}
onclick() {
this.setState(prev => ({ error: !prev.error }));
}
render() {
const textField = wire()
`
<div onconnected=${this} ondisconnected=${this}>
<input type="text" value="foo" onclick=${this}>
<label>bar</label>
</div>
`;
let finalNode;
if (this.state.error) {
finalNode = this.html `
<div>
${textField}
<p>
some name
</p>
</div>
`;
} else {
finalNode = this.html `
<div>
${textField}
</div>
`;
}
return finalNode;
}
}
document
.getElementById('root')
.appendChild(new Input().render());
I would expect it would it to render textField first and upon click to render p element along. I can see that render call is made but resulting element does not end up in DOM.
With hyper.Component the best way to go is to make the content conditional, not the root as a whole.
In order to do that, you can simply use a ternary, or an array.concat(...) or any other way that will update the component.
It's like the difference between updating an instance and replacing it.
From the inside, the component has no power to replace itself on its parent, unless you explicitly do so.
However, this example is easier than it looks like, and you can see it working on this Code Pen.
Moreover, if you don't specify onconnected and ondisconnected callbacks in the class, there's no need to specify them on the node.
const { hyper, wire } = hyperHTML;
class Input extends hyper.Component {
get defaultState() { return { error: false }; }
onclick() {
this.setState(prev => ({ error: !prev.error }));
}
render() { return this.html`
<div>
<div>
<input type="text" value="foo" onclick=${this}>
<label>bar</label>
</div>
${this.state.error ?
wire(this, ':extra')
`<p> some name </p>` :
null
}
</div>`;
}
}
document.body.appendChild(new Input().render());
I hope this helped.
I came across an issue using javascript plugin named flickity, it enables touch friendly slideshow images.
I initialize it like this:
import Flickity from 'flickity';
componentWillUnmount() {
this.flkty.destroy()
}
componentDidMount() {
this.flkty = new Flickity(this.refs.carousel, {
cellAlign: 'left',
})
}
It all works fine, but the issue is that div that it is targeting <div ref="carousel"></div>inside my render, is mutated to become flickity element. This sometimes results in Uncaught Invariant Violation error, when I re render components, hence I wanted to ask if there is a sollution to overcome this?
this is how I render related component
render() {
return (
<div ref="carousel" className="carousel">
{ this.props.src.map((image, index, array) => {
if(image.link != null) {
return (
<div key={index} className="card" style={ {backgroundImage: `url(${image.link})`} }>
<img
src={image.link} />
</div>
);
}
}) }
</div>
);
}
Was able to fix my issue by adding:
componentWillUpdate() {
this.flkty.destroy()
}
componentDidUpdate() {
this.flkty = new Flickity(this.refs.carousel, {
cellAlign: 'left',
})
}
I am making a conditional component that returns <a> if href is defined and returns <div> if not. I am doing this way:
const Label = ({children, ...other}) => {
let component;
if (other.href)
component =
<a {...other}>
{ children }
</a>
else
component =
<div {...other}>
{ children }
</div>
return component;
}
export default Label
There is any way to make this component by only changing the tag name? I know that if I use the manual way of creating compontents (React.createElement) I can do this by changing the first argument passed, for it's a string. But with JSX it's a little different. I though something like
let component =
<{tag} {...other}>
{ children }
</{tag}>
Is it possible?
something like this?
const Label = ({children, ...other}) => {
let component;
if (other.href) {
component = <a {...other}>{ children }</a>;
} else if (other.tag) {
const Tag = other.tag;
component = <Tag {...other}>{ children }</Tag>;
} else {
component = <div {...other}>{ children }</div>;
}
return component;
}
ReactDOM.render(
<Label href="#">Hello World</Label>,
document.getElementById('root')
);
ReactDOM.render(
<Label>Not a Link</Label>,
document.getElementById('rootTwo')
);
ReactDOM.render(
<Label tag="p">Paragraph</Label>,
document.getElementById('rootThree')
);
Working demo:, https://jsfiddle.net/tgm4htac/
If you pass to <> values like component.tag and JSX transpiler can create dynamic tags.
const Label = ({children, tag, ...other}) => {
const component = { tag: tag };
return (<component.tag {...other}>
{ children }
</component.tag>);
};
Example
or
const Label = ({children, tag, ...other}) => {
const Component = tag;
return (<Component {...other}>
{ children }
</Component>);
};
Example