how to add caption to images in draft js? - javascript

Is is possible to render an input (for adding caption to images) inside the draft js and can get the data which is typed by user? I am familiar with "custom block rendering" concept and followed the instruction which is provided by https://draftjs.org/docs/advanced-topics-custom-block-render-map/ But, when I wanted to write something in the input, I faced the below error:
invariant.js:40 Uncaught Invariant Violation: Unknown DraftEntity key: null.
In fact, block?.getEntityAt(0) returns null, since character list changes when I started to type.
This is the custom block renderer code:
import React from "react";
import { fromJS } from "immutable";
export const CustomBlockRenderer = (block, editorState, props) => {
if (block.getType() === "atomic") {
return {
component: Media,
editable: false,
};
}
return null;
};
const Image = (props) => {
if (!!props.src) {
return <img src={props.src} />;
}
return null;
};
const Media = (props) => {
const entity = props.contentState?.getEntity(props?.block?.getEntityAt(0));
const { src } = entity?.getData();
const type = entity?.getType();
let customBlock;
if (type === "image") {
customBlock = (
<figure className="custom-block__image-wrap">
<Image src={src?.url} className="custom-block__image" />
<figcaption className="custom-block__caption">{src?.caption}</figcaption>
</figure>
);
} else {
return null;
}
return customBlock;
};

I faced with a similar issue recently. Here the simplified demo of how it can work. Pay attention that EditorBlock component from draft-js is used on the image caption node. You should use EditorBlock in your custom component if you want an editable area inside the custom component.
class MyCustomBlock extends React.Component {
render() {
const imgSrc = this.props.block.get("data").src;
return (
<div className="my-custom-block">
<figure className="custom-block__image-wrap">
<img src={imgSrc} className="custom-block__image" />
<figcaption className="custom-block__caption">
<EditorBlock {...this.props} />
</figcaption>
</figure>
</div>
);
}
}

Related

React child callback not being executed after being passed down twice

I am working on the following project https://github.com/codyc4321/react-udemy-course section 11 the videos app. The udemy course is found at https://www.udemy.com/course/react-redux/learn/lecture/12531374#overview.
The instructor is passing a callback down to multiple children and calling it in the lowest videoItem and the code is supposed to console log something out. I have no console log in my browser even though I've copied the code as written and double checked for spelling errors.
At the main level is App.js:
import React from 'react';
import youtube from '../apis/youtube';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
class App extends React.Component {
state = {videos: [], selectedVideo: null};
onTermSubmit = async term => {
const response = await youtube.get('/search', {
params: {
q: term
}
});
// console.log(response.data.items);
this.setState({videos: response.data.items});
};
onVideoSelect = video => {
console.log('from the app', video);
}
render() {
return (
<div className="ui container">
<SearchBar onFormSubmit={this.onTermSubmit} />
<VideoList
onVideoSelect={this.onVideoSelect}
videos={this.state.videos} />
</div>
)
}
}
export default App;
videoList.js
import React from 'react';
import VideoItem from './VideoItem';
const VideoList = ({videos, onVideoSelect}) => {
const rendered_list = videos.map(video => {
return <VideoItem onVideoSelect={onVideoSelect} video={video} />
});
return <div className="ui relaxed divided list">{rendered_list}</div>;
};
export default VideoList;
the videoItem.js
import React from 'react';
import './VideoItem.css';
const VideoItem = ({video, onVideoSelect}) => {
return (
<div onClick={() => onVideoSelect(video)} className="item video-item">
<img
src={video.snippet.thumbnails.medium.url}
className="ui image"
/>
<div className="content">
<div className="header">{video.snippet.title}</div>
</div>
</div>
);
}
export default VideoItem;
The code that isn't running is
onVideoSelect = video => {
console.log('from the app', video);
}
My guess is that it has something to do with a key prop not being present in the map - I'm not super well versed with class components but I can't find anything else funky so maybe try adding a unique key prop in the map.
When rendering components through a map react needs help with assigning unique identifiers to keep track of re-renders etc for performance, that also applies to knowing which specific instance called a class method.
If you don't have a unique ID in the video prop you can use an index in a pinch, although ill advised, it can be found as the second parameter in the map function. The reason it's ill advised to use an index is if there are multiple children with the same index in the same rendering context, obviously the key parameter could be confused.
Okay-ish:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={index} onVideoSelect={onVideoSelect} video={video} />});
Better:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={video.id} onVideoSelect={onVideoSelect} video={video} />});

How to use forEach in react js

I want to create a function which iterate over all element with same class and remove a specific class.
It could be done easily using JavaScript.
const boxes = document.querySelectorAll(".box1");
function remove_all_active_list() {
boxes.forEach((element) => element.classList.remove('active'));
}
But how can I do this similar thing is ReactJs. The problem which I am facing is that I can't use document.querySelectorAll(".box1") in React but, I can use React.createRef() but it is not giving me all elements, it's only giving me the last element.
This is my React Code
App.js
import React, { Component } from 'react';
import List from './List';
export class App extends Component {
componentDidMount() {
window.addEventListener('keydown', this.keypressed);
}
keypressed = (e) => {
if (e.keyCode == '38' || e.keyCode == '40') this.remove_all_active_list();
};
remove_all_active_list = () => {
// boxes.forEach((element) => element.classList.remove('active'));
};
divElement = (el) => {
console.log(el);
el.forEach((element) => element.classList.add('active'))
};
render() {
return (
<div className="container0">
<List divElement={this.divElement} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import data from './content/data';
export class List extends Component {
divRef = React.createRef();
componentDidMount() {
this.props.divElement(this.divRef)
}
render() {
let listItem = data.map(({ title, src }, i) => {
return (
<div className="box1" id={i} ref={this.divRef} key={src}>
<img src={src} title={title} align="center" alt={title} />
<span>{title}</span>
</div>
);
});
return <div className="container1">{listItem}</div>;
}
}
export default List;
Please tell me how can I over come this problem.
The short answer
You wouldn't.
Instead you would conditionally add and remove the class to the element, the component, or to the collection.map() inside your React component.
Example
Here's an example that illustrates both:
import styles from './Example.module.css';
const Example = () => {
const myCondition = true;
const myCollection = [1, 2, 3];
return (
<div>
<div className={myCondition ? 'someGlobalClassName' : undefined}>Single element</div>
{myCollection.map((member) => (
<div key={member} className={myCondition ? styles.variant1 : styles.variant2}>
{member}
</div>
))}
</div>
);
};
export default Example;
So in your case:
You could pass active prop to the <ListItem /> component and use props.active as the condition.
Alternatively you could send activeIndex to <List /> component and use index === activeIndex as the condition in your map.
Explanation
Instead of adding or removing classes to a HTMLElement react takes care of rendering and updating the whole element and all its properties (including class - which in react you would write as className).
Without going into shadow dom and why react may be preferable, I'll just try to explain the shift in mindset:
Components do not only describe html elements, but may also contain logic and behaviour. Every time any property changes, at the very least the render method is called again, and the element is replaced by the new element (i.e. before without any class but now with a class).
Now it is much easier to change classes around. All you need to do is change a property or modify the result of a condition (if statement).
So instead of selecting some elements in the dom and applying some logic them, you would not select any element at all; the logic is written right inside the react component, close to the part that does the actual rendering.
Further reading
https://reactjs.org/docs/state-and-lifecycle.html
Please don't hessitate to add a comment if something should be rephrased or added.
pass the ref to the parent div in List component.
...
componentDidMount() {
this.props.divElement(this.divRef.current)
}
...
<div ref={this.divRef} className="container1">{listItem}</div>
then in App
divElement = (el) => {
console.log(el);
el.childNodes.forEach((element) => element.classList.add('active'))
}
hope this will work. here is a simple example
https://codesandbox.io/s/staging-microservice-0574t?file=/src/App.js
App.js
import React, { Component } from "react";
import List from "./List";
import "./styles.css";
export class App extends Component {
state = { element: [] };
ref = React.createRef();
componentDidMount() {
const {
current: { divRef = [] }
} = this.ref;
divRef.forEach((ele) => ele?.classList?.add("active"));
console.log(divRef);
window.addEventListener("keydown", this.keypressed);
}
keypressed = (e) => {
if (e.keyCode == "38" || e.keyCode == "40") this.remove_all_active_list();
};
remove_all_active_list = () => {
const {
current: { divRef = [] }
} = this.ref;
divRef.forEach((ele) => ele?.classList?.remove("active"));
// boxes.forEach((element) => element.classList.remove('active'));
console.log(divRef);
};
render() {
return (
<div className="container0">
<List divElement={this.divElement} ref={this.ref} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from "react";
import data from "./data";
export class List extends Component {
// divRef = React.createRef();
divRef = [];
render() {
let listItem = data.map(({ title, src }, i) => {
return (
<div
className="box1"
key={i}
id={i}
ref={(element) => (this.divRef[i] = element)}
>
<img src={src} title={title} align="center" alt={title} width={100} />
<span>{title}</span>
</div>
);
});
return <div className="container1">{listItem}</div>;
}
}
export default List;
Create ref for List component and access their child elements. When key pressed(up/down arrow) the elements which has classname as 'active' will get removed. reference

React dynamics image

i'm learning the react techologie, i'm the first exercice i must put the images like this, but when i use the map function to put the images i don't saw the images :/
image dynamics example
import React from 'react'<br>
import './Style/App.css'
const tabImg = ['./img/html.png', './img/css.png', './img/javascript.png', './img/logo192.png']<br>
const displayImgTech = tabImg.map((tech) => <img key={tech} src={tech} alt="techno"/>)<br>
export const Header = () => {
return (
<div className='container'>
<img src={displayImgTech} alt='technoFE'/>
</div>
)
}
Thank you
By passing displayImgTechin the src img props your are not adding an url but an "array" of img tags. so you just need to call {display take in your div like this:
const displayImgTech = tabImg.map((tech) => <img key={tech} src={tech} alt="techno"/>)<br>
export const Header = () => {
return (
<div className='container'>
{displayImgTech}
</div>
)
}

Displaying images from fetch API call Node/React

Im trying display images im fetching from API
btw url is defined
fetch(url).then((res) => {
res.blob()
.then((img) => {
console.log(img)
const objectURL = URL.createObjectURL(img);
const url = objectURL.replace(/[blob:]{5}/gi,'')
ReactDOM.render(<Gallery photos={url} />, document.getElementById('root'));
});
})
Gallery.js
import React, { Component } from 'react';
class Gallery extends Component {
constructor (props) {
super(props);
this.state = {
Images: []
}
}
componentDidMount () {
this.setState({images: this.props.photos});
}
render() {
const {image} = this.props.photos;
return (
<div >
<img
className="App"
width="300"
height="300"
src={image}
alt="dogs"/>
</div>
);
}
}
export default Gallery;
with or without the regex /[blob:]{5}/gi it is only displaying the alt prop and not the image. The image is received and the get call is successful but the objectURL url is not working. Any help?
const {image} = this.props.photos; is equivalent to
const image = this.props.photos.image;
It means, "assign the property image of this.props.photos to the variable image".
However, this.props.photos is a string. Strings don't have a image property. You simply want
const image = this.props.photos;
You are not doing anything with this.state.Images, so you can remove the constructor and componentDidMount.
/[blob:]{5}/gi doesn't do what you want it to do. It means "match all sequences of 5 characters that consist of b, l, o or :"
I.e. the character sequence bb:ol would match.
If you wanted to remove the sequence blob: at the beginning of the string, then you should be using /^blob:/i instead.
However, you shouldn't remove blob: from the URL.
Complete and simplified example
import React, { Component } from 'react';
function Gallery(props) {
return (
<div >
<img
className="App"
width="300"
height="300"
src={props.image}
alt="dogs"
/>
</div>
);
}
fetch(url)
.then(res => res.blob())
.then(img => {
ReactDOM.render(
<Gallery image={URL.createObjectURL(img)} />,
document.getElementById('root'),
);
})

React fetch img from slow api

I am sure that my question is obvious but I cannot find simple answer anywhere. I am not familiar with redux/flux so I don't know if I need to learn them to achieve my goal.
I get from my server urls to images I need to display on the component. I want to display loader till the image is fetched.
What is the best (and easiest) way to do that? Is necessary to use flux/redux?
May I use just fetch(image_URL).then... promise?
For now on just call url while rendering img html tag:
{this.props.data.images.map(img=>{
return(
<img src={img.url}/>
)})
how to manage async of this task? I already use apollo to fetch local db data. May I use apollo for fetching external data?
The easiest way is to define a loading flag and use it to determine if the loader should be rendered. It seems that your fetch logic somewhere else but the idea is the same.
class YourComponent() extends Component {
constructor() {
this.state = {
isLoading: false,
};
}
componentWillMount() {
this.setState({isLoading:true});
fetch('image_URL')
.then(res => {
this.setState({
images: res.images,
isLoading: false,
})
})
}
render() {
const { isLoading , images} = this.state;
if (isLoading) {
return <YourLoaderComponent />
}
return (
<div>
{images.map(img => <img src={img.url} />)}
</div>
);
}
}
You can make a use of onLoad react callback on the <img/> tag.
Tutorial:
Define React Component <LoadedComponent /> which will be a spinner.
Then you can define another React Component <ImageComponent /> which will have a default imageLoaded state set to false.
If imageLoaded is false, <ImageComponent/> will render img with width and height 0px.
The <img /> tag has onLoad binding to function imageLoaded() which then sets the imageLoaded state to true. When the state changes onLoad(when image finished loading) of <img/>, <ImageComponent/> automatically rerenders and it renders only <img/> with normal width and height.
import React from "react";
import { render } from "react-dom";
const LoaderComponent = () => (
<img
width="300"
height="300"
alt="Loading spinner"
src="http://blog.teamtreehouse.com/wp-content/uploads/2015/05/InternetSlowdown_Day.gif"
/>
);
const hiddenImageStyle = {
width: 0,
height: 0
};
class ImageComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
};
}
imageLoaded() {
this.setState({
loaded: true
});
}
render() {
if (this.state.loaded) {
return(
<div>
<img alt="Cat" src={this.props.url} width="300" height="300" />
</div>
)
}
return (
<div>
<img
alt="Cat"
src={this.props.url}
width="300"
height="300"
onLoad={() => this.imageLoaded()}
style={hiddenImageStyle}
/>
<LoaderComponent />
</div>
);
}
}
const imagesUrls = [
"https://c1.staticflickr.com/5/4200/34055408873_e9bf494e24_k.jpg",
"https://c1.staticflickr.com/5/4536/37705199575_ded3cf76df_c.jpg"
];
class App extends React.Component {
render() {
return (
<div>
{imagesUrls.map((url, index) => (
<ImageComponent key={index} url={url} />
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here you can see a working example: https://codesandbox.io/s/zwz9o84kn3
If you have a good Internet speed you will probably not notice the spinner. To see the spinner, the best is to open preview of the sandbox in a separate tab
and then open chrome dev tools and in the Network tab check disable cache and set a preset to Slow 3G
After refreshing you will notice loadining spinner, until the image will load

Categories

Resources