ReactJS Replace Broken Image with a Different Element - javascript

Is it possible to replace a broken image with a separate element entirely in reactJS?
My current code uses the onError() function to set a broken image's src
<img src={user.avatar} onError={e => e.target.src = '/static/image.png'} />
What I'd like to do is replace it with some text instead. Something like:
<img src={user.avatar} onError={() => this.replace() } />
replace function(){
return <div class='some-class'>Image not found</div> // Would replace the image element
}
Note* The user.avatar property will always be defined, and I'm not looking to use the alt attribute

Here's how I might do it for a simple image component. We just change what we return if there was an error.
export function UserImageComponent({user}){
const [isError,setIsError] = useState(false);
if(isError){
return <div class='some-class'>Image not found</div> // Would replace the image element
}
return <img src={user.avatar} onError={() => this.setIsError(true) } />
}

You can use this strategy:
class Image extends React.Component {
constructor() {
super();
this.state = {};
this.fallback = () => {
this.setState({ failed: true });
};
}
render() {
if (this.state.failed) {
return <div classname='some-class'>Image not found</div>;
} else {
return <img src={this.props.src} onError={this.fallback} />;
}
}
}
const brokenUrl = 'url.png';
const url = 'https://picsum.photos/536/354';
const app = (
<div>
<h2>Broken image:</h2>
<Image src={brokenUrl} />
<h2>Working image:</h2>
<Image src={url} />
</div>);
ReactDOM.render(app, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Related

Class extends using functions inside render?

I'm looking for a way to reduce code redundancy through class extending/inheritance on JavaScript/react.js.
For example, I want to have two types of UserCard components, that both represent a user's info, but one is for detailed (normal) view, other one is for list (mini) view. (e.g. imagine the normal one is something you may see on /user/:id and mini one is on /users)
Specifically, I wanted to have, for the normal ones, 1. its username and bio, icon, etc 2. its latset posts 3. extra actions (e.g. following/DM), and for the mini ones, exclude the 2. and 3. from the normal UserCard.
To implement the above model I thought I should use a JS extends or something (I'm not very familiar with extends), so basically I tried something similar to the following.
I know the following doesn't work, but I really don't have a good idea to how. What should I do with the issue? Thanks.
class UserCardBase extends Component {
constructor(props) {
super(props);
this.state = {
page_size: 3,
newly_order: true,
};
};
componentDidMount() {
{/* gets the user data */ }
axios.get(`/api/users/${this.props.id}`)
.then((response) => { this.setState({ user: response.data, updated: true, }); })
.catch((error) => { console.log(error); toast.error(() => <>failed.</>); });
};
render() {
const renderBlogcards = (props) => {
{/* renders blogcards */ }
return this.state.posts?.map((post, i) => <BlogCard key={i} data={post} />)
}
const extraActions = (props) => {
{/* enables follow/message */ }
return <div className="card__user-actions">
<button className="card__user-actions-follow">Follow</button>
<button className="card__user-actions-message">Message</button>
</div>
}
// mini sized usercard
const ContentMini = (props) => (
<>
<div className="__user_card">
<div className="card" >
<main className="card__user">
<img src={this.state.user?.userprofile.avatar} alt="" className="card__user-image"></img>
<div className="card__user-info">
<Link to={`/user/${this.state.user?.id}`}><h2 className="card__user-info__name">#{this.state.user?.username}</h2></Link>
<p className="card__user-info__desc">{this.state.user?.userbio.bio}</p>
</div>
</main>
</div>
</div>
</>
)
// default sized usercard
const Content = (props) => (
<>
<div className="__user_card">
<div className="card" >
<main className="card__user">
<img src={this.state.user?.userprofile.avatar} alt="" className="card__user-image"></img>
<div className="card__user-info">
<Link to={`/user/${this.state.user?.id}`}><h2 className="card__user-info__name">#{this.state.user?.username}</h2></Link>
<p className="card__user-info__desc">{this.state.user?.userbio.bio}</p>
</div>
<extraActions />
</main>
<renderBlogcards />
</div>
</div>
</>
)
return (
<>
{/* by default, uses the mini usercard */ }
<ContentMini />
</>
);
}
}
class UserCard extends UserCardBase {
constructor(props) {
super(props);
};
render() {
return (
<>
{/* assume trying overrides the render so uses the normal usercard replacing the mini */ }
<Content />
</>
)
}
}

ReactJs: Refresh img tag after image update where url remains constant but the image from that url changes

In my app, when user changes image, the url of the image remains the same, but the image in the cloud changes.
In other words, suppose the url of the image is URL1, after the user uploads his image, the image retrieved from that url changes, but the URL remains the same.
The problem with this is that React does not detect the change, and so does not refresh the image tag automatically, and I have to refresh the page, in order to see the new image.
Here's my code:
class ProfilePage extends Component {
saveImageUrlInDatabase(profileImageURL) {
const imageData = {
profileImageURL: profileImageURL,
};
this.props.uploadProfilePictureURL(imageData);
}
async updateAvatar(event) {
const imageFile = event.target.files[0];
if (!imageFile) {
return;
}
const imageURL = await this.props.uploadProfileImage(imageFile);
this.saveImageUrlInDatabase(imageURL);
this.setState({
profileImageURL: imageURL,
});
}
render() {
const { profile, loading } = this.props.profile;
if (!profile || loading) {
profileContent = <Spinner />;
} else {
// #BUG: Even though profileImageSrc changes
// It doesn't get update automatically
// It turns out the url does not change
// But, the image does change
let profileImageSrc;
// True if user has updated his image
if (this.state.profileImageURL !== "") {
profileImageSrc = this.state.profileImageURL;
} else {
profileImageSrc = !profile.profileImageURL
? require("assets/img/faces/lofi-girl.png")
: profile.profileImageURL;
}
profileContent = (
<Container>
<div className="owner">
<div className="avatar">
<Label for="avatar-upload">
<img
alt="..."
className="img-circle img-no-padding img-responsive"
src={profileImageSrc}
key={Math.floor(Math.random() * 10)}
style={{
cursor: "pointer",
}}
title="Change profile image"
/>
</Label>
<input
id="avatar-upload"
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={this.updateAvatar}
/>
</div>
</div>
</Container>
);
}
return <div className="section profile-content">{profileContent}</div>;
}
}
Any idea how to solve this?
I was facing the same problem: I was updating the image in a url but the url was the same. The image didn't update because the brower saved the image in caché. What I'm doing is to add a random number to the end of the url. If the component is different, it will update alone; otherwise, you can add a button to update the random number. Something like:
const [random, setRandom] = React.useState(1)
render
<button onClick={()=>setRandom(Math.random())}>
update image
<button/>
<img
className = 'img-miniatura'
src = {url+'?n='+random}
alt='miniatura'
/>
I think you are not declare to React what your state variables are. Normally you need to define the state as described in the docs of React https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
You would need to do something like this:
constructor(props) {
super(props);
this.state = {profileImageSrc: //the initial url you want}
}
Your component has some issue, try this:
import fallbackImage from "./assets/img/faces/lofi-girl.png";
class ProfilePage extends Component {
constructor(props) {
super(props)
this.state = {
profileImageURL: props.profile.profileImageURL,
}
}
saveImageUrlInDatabase(profileImageURL) {
const imageData = {
profileImageURL: profileImageURL,
};
this.props.uploadProfilePictureURL(imageData);
}
async updateAvatar(event) {
const imageFile = event.target.files[0];
if (!imageFile) {
return;
}
const imageURL = await this.props.uploadProfileImage(imageFile);
this.saveImageUrlInDatabase(imageURL);
this.setState({
profileImageURL: imageURL,
});
}
render() {
const { profile, loading } = this.props.profile;
const { profileImageURL } = this.state;
return (
<div className="section profile-content">
{!profile || loading && <Spinner />}
<Container>
<div className="owner">
<div className="avatar">
<Label for="avatar-upload">
<img
alt="..."
className="img-circle img-no-padding img-responsive"
src={
profileImageURL ?
profileImageURL :
fallbackImage
}
key={Math.floor(Math.random() * 10)}
style={{
cursor: "pointer",
}}
title="Change profile image"
/>
</Label>
<input
id="avatar-upload"
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={this.updateAvatar}
/>
</div>
</div>
</Container>
</div>
)
}
}

How do I get the value from a React Img component

I keep getting undefined from the console.log in 'handleClickVideo'. How can I get the value out of clicking on a video properly? I tried with a div also however div doesn't have a value property. I thought Img did though.
const Videos = ({ videos }) => {
const handleClickVideo = (event) => {
console.log(event.target.value)
}
return (
<>
<h2 className="title is-5">Videos</h2>
<div className="columns is-multiline">
<VideoModal
videoOpen={videoOpen}
setVideoClose={handleClickVideo}
/>
{
videos.map((video, key) => {
return (
<div className="column is-one-third">
<div className={styles.thumbnail}>
<Img src={`https://img.youtube.com/vi/${video.link}/0.jpg`} onClick={handleClickVideo} value={video.link}/>
</div>
<p className={styles.videoTitle}>Green Book Official Trailer</p>
</div>
)
})
}
</div>
</>
)
}
You're calling handleClickVideo in VideoModal but VideoModal doesn't have any value, so it will be undefined in your callback
<VideoModal
videoOpen={videoOpen}
setVideoClose={handleClickVideo}
/>
You can make your callback function to accept a value:
const handleClickVideo = (video) => {
console.log(video)
}
And then update your render function:
<VideoModal
videoOpen={videoOpen}
setVideoClose={() => handleClickVideo(0)}
/>
<Img
src={`https://img.youtube.com/vi/${video.link}/0.jpg`}
onClick={()=>handleClickVideo(video.link)}
/>

How to make a image clickable using document.getElementById in reactjs

I am currently using the Twitch API, where I have created a file that renders the game cover image by searching. I want the user to be able to click the game image, which will redirect them to their proper Twitch Links
Search Response
My code for the game image rendering looks like this:
render() {
const { game } = this.props
return (
<div className="GameDetails">
<img src={this.formatImageUrl(game.box_art_url)} alt="" />
<p>{game.name} </p>
<p>ID: {game.id}</p>
</div>
)
}
}
export default GameImage
I tried out:
render() {
const { game } = this.props
return (
<div className="GameDetails">
<img src={this.formatImageUrl(game.box_art_url)} alt="" onClick${"https://www.twitch.tv/directory/game/${document.getElementById("SearchName").value}"}/>
<p>{game.name} </p>
<p>ID: {game.id}</p>
</div>
)
}
}
export default GameImage
Which gives me an error.
The "SearchName" value is what the user types in the search bar for the game, therefore I want to send them to the respectable twitch pages when clicked.
Of course you will receive an error, because firstly you've misspelled $ with = and secondly, onClick prop expects a function which will handle the action after clicking the image.
Suggested approach:
handleClick = () => {
// logic when user clicks on image
// https://www.twitch.tv/directory/game/${document.getElementById("SearchName").value}
}
render() {
const { game } = this.props
return (
<div className="GameDetails">
<img src={this.formatImageUrl(game.box_art_url)} alt="" onClick={this.handleClick} />
<p>{game.name} </p>
<p>ID: {game.id}</p>
</div>
)
}
export default GameImage
Edit: It's kinda difficult to understand what you really want to achieve, however if you want that img to work as a link, you should consider using a element instead. Just wrap your img tag as well as p into a.
render() {
const { game} = this.props
const link = `https://www.twitch.tv/directory/game/${document.getElementById("SearchName").value}`;
return (
<div className="GameDetails">
<a href={link}>
<img src={this.formatImageUrl(game.box_art_url)} alt="" onClick={this.handleClick} />
<p>{game.name} </p>
<p>ID: {game.id}</p>
</a>
</div>
)
}
If all you want to do is go to another site by clicking on the image simply wrap it in an HTML anchor with the url as the href attribute. Hover over the images (don't click on them) to see the URL in the browser status bar.
function App({ data }) {
return data.map(game => <Details game={game} />);
}
function Details({ game }) {
return (
<div className="gameDetails">
<a href={game.twitch_url}>
<img src={game.box_art_url} />
</a>
</div>
);
}
const data = [
{ id: 1, twitch_url: 'http://game1.com', box_art_url: 'https://dummyimage.com/100x100/000/fff' },
{ id: 2, twitch_url: 'http://game2.com', box_art_url: 'https://dummyimage.com/100x100/555/fff' },
];
ReactDOM.render(
<App data={data} />,
document.getElementById('container')
);
.gameDetails {
display: inline-block;
padding: 0.3em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container"></div>

React click on item to show details

Iam new to React and I'm trying to interact with the swapi API.
I want to get the list of films (movie titles list) and when I click on a title to show the opening_crawl from the json object.
I managed to get the film titles in an array. I don't know how to proceed from here.
Here is my code:
class StarWarsApp extends React.Component {
render() {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
class Movies extends React.Component {
constructor(props) {
super(props);
this.handleMovies = this.handleMovies.bind(this);
this.state = {
movies: []
};
this.handleMovies();
}
handleMovies() {
fetch("https://swapi.co/api/films")
.then(results => {
return results.json();
})
.then(data => {
console.log(data);
let movies = data.results.map(movie => {
return <div key={movie.episode_id}>{movie.title}</div>;
});
this.setState(() => {
return {
movies: movies
};
});
});
}
render() {
return (
<div>
<h1>Episodes</h1>
<div>{this.state.movies}</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("app"));
To iterate over movies add this in render method:
render(){
return (
<div>
<h1>Episodes</h1>
{
this.state.movies.map((movie, i) => {
return (
<div className="movie" onClick={this.handleClick} key={i}>{movie.title}
<div className="opening">{movie.opening_crawl}</div>
</div>
);
})
}
</div>
);
}
Add this method to your Movies component to add active class on click to DIV with "movie" className:
handleClick = event => {
event.currentTarget.classList.toggle('active');
}
Include this css to your project:
.movie .opening {
display: none;
}
.active .opening {
display: block
}
After fetching the data, just keep it in your state then use the pieces in your components or JSX. Don't return some JSX from your handleMovies method, just do the setState part there. Also, I suggest using a life-cycle method (or hooks API maybe if you use a functional component) to trigger the fetching. By the way, don't use class components unless you need a state or life-cycle methods.
After that, you can render your titles in your render method by mapping the movies state. Also, you can have a place for your opening_crawls part and render it with a conditional operator. This condition changes with a click. To do that you have an extra state property and keep the movie ids there. With the click, you can set the id value to true and show the crawls.
Here is a simple working example.
const StarWarsApp = () => {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
const Header = ({ title }) => (
<div>
<h1>{title}</h1>
</div>
);
class Movies extends React.Component {
state = {
movies: [],
showCrawl: {}
};
componentDidMount() {
this.handleMovies();
}
handleMovies = () =>
fetch("https://swapi.co/api/films")
.then(results => results.json())
.then(data => this.setState({ movies: data.results }));
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
render() {
return (
<div>
<h1>Episodes</h1>
<div>
{this.state.movies.map(movie => (
<div
key={movie.episode_id}
id={movie.episode_id}
onClick={this.handleCrawl}
>
{movie.title}
{this.state.showCrawl[movie.episode_id] && (
<div style={{ border: "1px black solid" }}>
{movie.opening_crawl}
</div>
)}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I am using id on the target div to get it back from the event object. I don't like this method too much but for the sake of clarity, I used this. You can refactor it and create another component may be, then you can pass the epoisde_id there and handle the setState part. Or you can use a data attribute instead of id.

Categories