How to show loader in reactjs and meteor? - javascript

I have a page where 3 recipes are listed along with more button. When more button is clicked more 3 recipes are listed. What i am trying to do is before listing more 3 recipes i want to show a spinner/loader icon but i could only change the text of button. How can i show loader icon as soon as more button is clicked and before those additional 3 recipes are listed. I am using meteorjs and reactjs.
my code for this is
export default class Home extends Component {
constructor(){
super();
this.state = { limit:3 , loading:false }
this.addMore = this.addMore.bind(this);
}
getMeteorData(){
let data = {};
data.recipes = [];
data.recipes = Recipes.find({},{sort:{createdAt:-1}}).fetch();
let recipeHandle = Meteor.subscribe('recipelist',this.state.limit);
if(recipeHandle.ready()){
data.recipes = Recipes.find({},{sort:{createdAt:-1},limit:this.state.limit}).fetch();
}
return data;
}
addMore(){
this.setState({
limit:this.state.limit + 3, loading: true }, () => {
this.setTimeout(()=>{
this.setState({loading:false});
},2000);
});
}
render() {
console.log('this.data.recipes',this.data.recipes);
let recipes = _.map(this.data.recipes,(recipe) => {
return <RecipeList key={recipe._id} recipe={recipe} loading={this.state.loading} />
});
return (
<div className="row">
<div className="intro blink z-depth-1">
<div className="row">
<div className="col l7">
<h1 className="heading flow-text blink">Sell your Recipe</h1>
</div>
</div>
</div>
<div className="row">
<div className="col s12">
{recipes}
</div>
</div>
<button onClick={this.addMore} type="button" className="btn coral more">More</button>
</div>
);
}
}
ReactMixin(Home.prototype, ReactMeteorData);

You could remove the loading state, and just compute the loading state from the data: {this.state.limit !== this.data.recipes.length && <img src="path_to_loader.gif" />}
I am not sure where you want to show the loader, but for example you could do:
render() {
console.log('this.data.recipes',this.data.recipes);
let recipes = _.map(this.data.recipes,(recipe) => {
return <RecipeList key={recipe._id} recipe={recipe} loading={this.state.loading} />
});
return (
<div className="row">
<div className="intro blink z-depth-1">
<div className="row">
<div className="col l7">
<h1 className="heading flow-text blink">Sell your Recipe</h1>
</div>
</div>
</div>
<div className="row">
<div className="col s12">
{recipes}
</div>
</div>
{this.state.limit !== this.data.recipes.length && <img src="path_to_loader.gif" />}
<button onClick={this.addMore} type="button" className="btn coral more">More</button>
</div>
);
}

Related

React issue with show / hiding elements

This project is using React.
The goal is that when the maximize icon is clicked on the the Editor component, the Preview component will be hidden. When the maximize icon is clicked on the Preview component, the Editor component will be hidden.
The problem is, when I click the maximize icon on the Editor component, the only thing that displays is the text "not found." But the Preview maximize icon works when clicked.
I logged state to the console so I know that the state is updating when the editor button is clicked, but I can't figure out what is wrong with the way I am rendering the Editor element.
Codepen link: https://codepen.io/Jamece/pen/Exbmxmv
Thank you for any help you can provide.
import * as marked from "https://cdn.skypack.dev/marked#4.0.12";
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
editorOnly: false,
previewOnly: false,
inputValue: "",
outputValue: ""
};
this.handleChange = this.handleChange.bind(this);
this.editorChange = this.editorChange.bind(this);
this.previewChange = this.previewChange.bind(this);
}
handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
//changes view to editorOnly when editor maximize button is clicked then back to full view when clicked again
editorChange() {
this.setState((state) => {
if (state.editorOnly === false) {
return { editorOnly: true };
} else {
return { editorOnly: false };
}
});
}
//changes view to previewOnly when preview maximize button is clicked then back to full view when clicked again
previewChange() {
this.setState((state) => {
if (state.previewOnly === false) {
return { previewOnly: true };
} else {
return { previewOnly: false };
}
});
}
render() {
console.log(this.state);
if (this.state.editorOnly === false && this.state.previewOnly === false) {
return (
<div className="container-fluid px-0">
<div className="d-flex flex-column main">
<Editor editorChange={this.editorChange}
handleChange={this.handleChange}/>
<Preview previewChange={this.previewChange} />
</div>
</div>
);
} else if (
this.state.editorOnly === true &&
this.state.previewOnly === false
) {
return (
<div className="container-fluid px-0">
<div className="d-flex flex-column main">
<Editor editorChange={this.editorChange}
handleChange={this.handleChange}/>
</div>
</div>
);
} else if (
this.state.editorOnly === false &&
this.state.previewOnly === true
) {
return (
<div className="container-fluid px-0">
<div className="d-flex flex-column main">
<Preview previewChange={this.previewChange} />
</div>
</div>
);
}
else {
return(
<div></div>
)
}
}
}
class Editor extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="d-flex justify-content-center">
<form>
<div className="boxes">
<div className="d-flex align-items-center label-bar">
<div className="leftcon">
<i className="fa-solid fa-book"></i>
</div>
<div className="headings">Editor</div>
<div className="rightcon">
<button className="btn" onClick={this.props.editorChange}>
<i className="fa-solid fa-maximize"></i>
</button>
</div>
</div>
<textarea
value={this.props.inputValue}
onChange={this.props.handleChange}
></textarea>
</div>
</form>
</div>
);
}
}
class Preview extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div className="d-flex justify-content-center">
<form>
<div className="boxes">
<div className="d-flex align-items-center label-bar">
<div className="leftcon">
<i className="fa-solid fa-book"></i>
</div>
<div className="headings">Preview</div>
<div className="rightcon">
<button className="btn" onClick={this.props.previewChange}>
<i className="fa-solid fa-maximize"></i>
</button>
</div>
</div>
<div className="preview">
<br /> <br /> <br /> <br /> <br /> <br /> <br /> <br />
</div>
</div>
</form>
</div>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById("app"));
A button element inside a form element by default has type="submit".
Hence, when you click the maximize button it tries to submit the form, making an http request.
This is not what you want here so you should set type="button" on your buttons. This way they will not trigger a form submission on click.
The same thing happens on your Preview component, but note that in the console you get the following message:
Form submission canceled because the form is not connected
I believe this is because the way you order the elements in the different states causes React to recreate the preview window in the DOM. If you switch Editor and Preview around in the state where both are visible then Editor works fine and Preview is broken.

Display/delete component on click

So, im trying to display my component named documentReader inside div with class desktop-app-grid by clicking and icon below, but icon is also a component, i tried doing this by using state, but i don't know how I can do this. I'm dropping my code below with hope someone can help me.
I got this:
<div className="desktop">
<div
className="desktop-app-grid"
>
</div>
<div className="taskbar">
<div className="taskbar-content">
<div className="apps">
<TaskbarAppIcon
appName="documentReader"
icon={icon}
title="My CV"
/>
</div>
<div className="status">
<Clock className="clock" />
</div>
</div>
</div>
</div>
);
}
And on click i want to get displayed in desktop-app-grid like this:
<div
className="desktop-app-grid"
>
<documentReader />
</div>
icon.js (code isn't complete)
class TaskbarAppIcon extends React.Component {
constructor() {
super();
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
const icon = document.querySelector("img");
icon.classList.toggle("icon-active");
setTimeout(() => {
icon.classList.toggle("icon-active");
}, 200);
this.setState({
clicked: true
});
}
render(){
const classes = this.props.appName + "Icon icon";
return (
<div className={classes} onClick={this.handleClick}>
<img src={this.props.icon} alt={this.props.appName} title={this.props.title} className="icon-image"></img>
<div className="isActive"></div>
</div>
);
}
}
export default TaskbarAppIcon;
is there any function that works like innerHTML, but isn't a dangerouslyInnerHTML?
what you need to do is move your handleClick and clicked state to the parent component where you rendering TaskbarAppIcon. Being more specific where you have this code:
<div className="desktop">
<div className="desktop-app-grid">
</div>
<div className="taskbar">
<div className="taskbar-content">
<div className="apps">
<TaskbarAppIcon
appName="documentReader"
icon={icon}
title="My CV"
/>
</div>
<div className="status">
<Clock className="clock" />
</div>
</div>
</div>
</div>
So for example, the above code is in you App component, so you need to let it like this:
class App extends React.Component {
constructor() {
super();
this.state = {
clicked: false,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
const icon = document.querySelector("img");
icon.classList.toggle("icon-active");
setTimeout(() => {
icon.classList.toggle("icon-active");
}, 200);
this.setState({
clicked: !this.state.clicked,
});
};
render() {
return (
<div className="desktop">
<div className="desktop-app-grid">
// here's the trick, if your clicked state is TRUE it will show <documentReader />
{this.state.clicked && <documentReader />}
</div>
<div className="taskbar">
<div className="taskbar-content">
<div className="apps">
<TaskbarAppIcon
// Here you are specifying that TaskbarAppIcon has a prop handleClick and its a function
handleClick={this.handleClick}
appName="documentReader"
icon={icon}
title="My CV"
/>
</div>
</div>
</div>
</div>
);
}
}
And in your TaskbarAppIcon component you just need to change
<div className={classes} onClick={this.handleClick}>
to
<div className={classes} onClick={this.props.handleClick}>

React Error: Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null

EDIT: [SOLVED by the community - in the answers!] Thanks everyone
I'm trying to add search functionalities to my webApp (a list of the movies I own). To do this, from the app I'm calling a functional component (MovieListFiltered) which has the following code:
MovieListFiltered.js:
import React from 'react'
const MovieListFiltered = (props) => {
const newData = props.moviesAfterFilter
if(newData !== null) {
const newMovies = newData.map((movie, i) =>
{
return(
<div className="col s12 m3 l3" key={i} movieid ={movie.idFromTmdb}>
<div className="card">
<div className="card-image waves-effect waves-block waves-light">
<img src={movie.url2poster} alt={movie.movieTitle} className="responsive-img" />
<p className="littleFont" align="center"><span><b>{movie.movieTitle}</b></span></p>
</div>
<div className="card-action">
<a href="#" onClick={() => this.props.viewMovieInfo(movie.idFromTmdb)}>Movie Details</a>
</div>
</div>
</div>
);
})
console.log(newMovies)
props.movieCallback(newData, newMovies);
} else {
return null
}
}
export default MovieListFiltered
So, basically, notything special there: you see many console.log calls, that was just to make sure the correct array of data was passed (and it is!)
In App.js:
... code not interesting goes here ...
callbackFromList = (childDataData, childDataMovies) => {
this.setState({moviesToFilter: childDataData});
this.setState({moviesToShow: childDataMovies});
this.setState({totalResults: childDataData.length});
}
render()
{
... some not interesting code goes here...
return(
<div className="App">
<Nav />
<div>
<div className="container">
<div className="row">
<div className="col s10 offset-s1">
<MovieListFiltered viewMovieInfo={this.viewMovieInfo} movieCallback={() => this.callbackFromList} ref={this.movieListRef} moviesAfterFilter={this.state.moviesFiltered}></MovieListFiltered>
</div>
</div>
</div>
</div>
</div>
);
}
Can you please help me? I've read all the questions already made here on stackoverflow, but nothing seems to fit to my case.
I think you are wanting something like this:
const MovieListFiltered = (props) => {
const newData = props.moviesAfterFilter
if(newData !== null) {
const newMovies = newData.map((movie, i) => (
<div className="col s12 m3 l3" key={i} movieid ={movie.idFromTmdb}>
<div className="card">
<div className="card-image waves-effect waves-block waves-light">
<img src={movie.url2poster} alt={movie.movieTitle}
className="responsive-img" />
<p className="littleFont" align="center"><span><b>
{movie.movieTitle}</b></span></p>
</div>
<div className="card-action">
<a href="#" onClick={() =>
this.props.viewMovieInfo(movie.idFromTmdb)}>Movie Details</a>
</div>
</div>
</div>
);
)
console.log(newMovies)
props.movieCallback(newData, newMovies)
return newMovies
} else {
return null
}
}
Here a cleaner version with only one return. That may not be what you're looking for though.
import React from 'react'
const MovieListFiltered = (props) => {
const newData = props.moviesAfterFilter || []; // add 'or' if null or undefined
const newMovies = newData.map((movie, i) => (
<div className="col s12 m3 l3" key={i} movieid ={movie.idFromTmdb}>
<div className="card">
<div className="card-image waves-effect waves-block waves-light">
<img src={movie.url2poster} alt={movie.movieTitle} className="responsive-img" />
<p className="littleFont" align="center"><span><b>{movie.movieTitle}</b></span></p>
</div>
<div className="card-action">
<a href="#" onClick={() => this.props.viewMovieInfo(movie.idFromTmdb)}>Movie Details</a>
</div>
</div>
</div>
));
console.log(newMovies)
props.movieCallback(newData, newMovies);
return newMovies;
}
export default MovieListFiltered
You are only returning a value from the else block. The if block is not currently returning anything. You probably want to make the last line of the if block return newMovies;
import React from 'react'
const MovieListFiltered = (props) => {
const newData = props.moviesAfterFilter
if(newData !== null) {
const newMovies = newData.map((movie, i) =>
{
return(
<div className="col s12 m3 l3" key={i} movieid ={movie.idFromTmdb}>
<div className="card">
<div className="card-image waves-effect waves-block waves-light">
<img src={movie.url2poster} alt={movie.movieTitle} className="responsive-img" />
<p className="littleFont" align="center"><span><b>{movie.movieTitle}</b></span></p>
</div>
<div className="card-action">
<a href="#" onClick={() => this.props.viewMovieInfo(movie.idFromTmdb)}>Movie Details</a>
</div>
</div>
</div>
);
});
console.log(newMovies);
props.movieCallback(newData, newMovies);
return newMovies;
}
return null;
}
export default MovieListFiltered
Also, you might notice I got rid of the entire else block - this is because it's not necessary if you return from the corresponding if block.

Add +1 to willWatch when <a> is clicked

Task: add +1 to willWatch when <a> is clicked.
I have an error when <a> is clicked, because MovieItem is not a component. I try to set class MovieItem... but I have a problem with moviesData
import React, { Component } from "react";
import { moviesData } from "../moviesData";
function MovieItem(props) {
let {info : { id, vote_count , video, vote_average, title, popularity, poster_path, original_language, original_title ,backdrop_path, adult, overview} } = props;
return (
<div className="col" id={id} style={{width: "18rem"}}>
<img className="card-img-top" src={'https://image.tmdb.org/t/p/w500' + poster_path}
alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">Оригинальное название: {original_title}</h5>
<h5 className="card-title">Название: {title}</h5>
<p className="card-text">{overview}</p>
<p className="card-text">Рейтинг: {vote_average}</p>
<p className="card-text">Популярность: {popularity}</p>
<p className="card-text">Наличие видео: {video}</p>
<p className="card-text">Оригинальный язык: {original_language}</p>
<p className="card-text">Возраст 18+: {adult}</p>
<p className="card-text">backdrop_path {backdrop_path}</p>
<p className="card-text">Голоса: {vote_count}</p>
<a
// onClick={this.props.counter}
href="#"
className="btn btn-primary">Will Watch
</a>
</div>
</div>
)
}
class App extends Component {
constructor(state) {
super(state);
this.state = {
willWatch: 0
};
this.counter = this.counter.bind(this)
}
counter(e) {
e.preventDefault();
this.state.willWatch = this.state.willWatch + 1
}
render() {
return (
<div className="container">
<div className="col-12">
<div className="row">
<div className="col-9">
<div className="row">
{
moviesData.map((props) => {
return <MovieItem info={props} counter={this.counter}/>
})
}
</div>
</div>
<div className="col-3 sidebar">
<div className="row">
<p> Хочу посмотреть, фильмов: {this.state.willWatch} </p>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default App;
First of all, you have to destructure count from props like so:
function MovieItem(props) {
let {info : { id, vote_count , video, vote_average, title, popularity, poster_path, original_language, original_title ,backdrop_path, adult, overview}, counter } = props;
return (
<div className="col" id={id} style={{width: "18rem"}}>
<img className="card-img-top" src={'https://image.tmdb.org/t/p/w500' + poster_path}
alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">Оригинальное название: {original_title}</h5>
<h5 className="card-title">Название: {title}</h5>
<p className="card-text">{overview}</p>
<p className="card-text">Рейтинг: {vote_average}</p>
<p className="card-text">Популярность: {popularity}</p>
<p className="card-text">Наличие видео: {video}</p>
<p className="card-text">Оригинальный язык: {original_language}</p>
<p className="card-text">Возраст 18+: {adult}</p>
<p className="card-text">backdrop_path {backdrop_path}</p>
<p className="card-text">Голоса: {vote_count}</p>
<a
onClick={counter}
href="#"
className="btn btn-primary">Will Watch
</a>
</div>
</div>
)
}
and one more note, you have to set state using setState function:
counter(e) {
e.preventDefault();
this.setState((prevState) => {
return {
willWatch: state.willWatch + 1
}
});
}
You should never mutate/set this.state value directly.
Or else React would not know whether state has been changed or not.
(Refer to this article for details).
So instead of updating willWatch directly,
counter(e) {
e.preventDefault();
this.state.willWatch = this.state.willWatch + 1
}
Use setState
counter(e) {
e.preventDefault();
this.state(prevState => ({willWatch: prevState.willWatch + 1}));
}

How can I render a ReactJS component (only once) with an async call when I submit a form?

I am creating an app that searches for a movie. Everything is in one class now. I want to create a "Movie" component that only renders once when I search a movie. I have an async call to OMDB when the movie renders. I only want that to happen once and only when I click the search button.
So far, I have it working, only ReactJS renders the Movie component over and over again, which means my app continuously sends the API call to the OMDB database. This is my code:
import React, { Component } from 'react';
import { BrowserRouter as Router, Link, NavLink } from 'react-router-dom';
import Route from 'react-router-dom/Route';
import './App.css';
import language from './Components/Language';
import Navbar from './Components/Navbar';
import NavbarSimple from './Components/NavbarSimple';
class App extends Component {
constructor(){
super();
this.state = {
movieSearchTerm: '',
error: '',
languageIndex: 0,
searchSubmitted: false,
title: "",
year: "",
rated: "",
released: "",
runtime: "",
genre: "",
director: "",
writer: "",
actors: "",
plot: "",
language: "",
awards: "",
poster: "",
list_ratings: "",
imdbRating: "",
imdbVotes: "",
type: ""
}
this.onChangeMovieInput = this.onChangeMovieInput.bind(this);
this.onSubmitMovieForm = this.onSubmitMovieForm.bind(this);
this.switchLanguageTo = this.switchLanguageTo.bind(this);
this.setSearchSubmittedTo = this.setSearchSubmittedTo.bind(this);
}
switchLanguageTo(id){
this.setState({
languageIndex: id
});
}
getMovie(title){
console.log("Searching movie '" + title + "'");
var xhr = new XMLHttpRequest();
var json_obj, status = false;
xhr.open("GET", "http://www.omdbapi.com/?t=" + title + "&apikey=6c3a2d45", true);
xhr.onload = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var movie = JSON.parse(xhr.responseText);
status = true;
console.log(movie);
if(movie.Error){
this.setState({
error: movie.Error
});
} else {
this.setState({
title: movie.Title,
year: movie.Year,
rated: movie.Rated,
released: movie.Released,
runtime: movie.Runtime,
genre: movie.Genre,
director: movie.Director,
writer: movie.Writer,
actors: movie.Actors,
plot: movie.Plot,
language: movie.Language,
awards: movie.Awards,
poster: movie.Poster,
// list_ratings: movie.Ratings // Need to come up with a solution to render arrays as csv.
imdbRating: movie.imdbRating,
imdbVotes: movie.imdbVotes,
type: movie.Type
});
}
} else {
console.log(xhr.status + ": " + xhr.statusText);
}
}
}.bind(this);
xhr.onerror = function (e) {
console.log(xhr.status + ": " + xhr.statusText);
};
xhr.send(null);
}
onChangeMovieInput(e){
this.setState({
movieSearchTerm: e.target.value
})
}
onSubmitMovieForm(e){
e.preventDefault();
console.log("Movie to be searched: " + this.state.movieSearchTerm);
this.setState({searchSubmitted: true});
// this.getMovie(this.state.movieSearchTerm);
}
setSearchSubmittedTo(value){
this.setState({searchSubmitted: value});
}
render() {
if(this.state.searchSubmitted === false){
return (
<div>
<Navbar languageIndex={this.state.languageIndex} switchTaal={this.switchLanguageTo} />
<div className="App container-fluid mt-large">
{/* START SEARCH COMPONENT */}
<div className="row">
<div className="col-xs-12">
<form onSubmit={this.onSubmitMovieForm}>
<input type="text" className="width-100p" onChange={this.onChangeMovieInput} />
<button type="submit" className="width-100p btn btn-primary">Search</button>
</form>
</div>
</div>
{/* EINDE SEARCH COMPONENT */}
<div className="row">
{/* START MOVIE COMPONENT */}
<div className="movie">
<div className="row">
<div className="col-xs-12">
<p>{language[this.state.languageIndex].searchMovieInstruction} <span className={this.state.error===""?"hidden":"visible"}>Error: {this.state.error}</span></p>
</div>
</div>
</div>
{/* EINDE MOVIE COMPONENT */}
</div>
</div>
</div>
);
// EINDE RETURN
} else {
return (
<div>
<Navbar languageIndex={this.state.languageIndex} switchTaal={this.switchLanguageTo} />
<div className="App container-fluid mt-large">
{/* START SEARCH COMPONENT */}
<div className="row">
<div className="col-xs-12">
<form onSubmit={this.onSubmitMovieForm}>
<input type="text" className="width-100p" onChange={this.onChangeMovieInput} />
<button type="submit" className="width-100p btn btn--primary">Search</button>
</form>
</div>
</div>
{/* EINDE SEARCH COMPONENT */}
<div className="row">
{/* I WANT THIS COMPONENT TO RENDER AS "<Movie title={this.state.movieSearchTerm} /> only when I click on search. The movie component then makes a call to the OMDB resulting in the search result. */}
{/* START MOVIE COMPONENT */}
<div className="movie">
<div className="row">
<div className="col-xs-12 col-sm-3 col-md-2">
<img src={this.state.poster} className="img-responsive movie--image" />
</div>
<div className="col-xs-12 col-sm-9 col-md-10">
<div className="flex flex-justify-between flex-align-center">
<div className="flex-padding-left">
<h3>{this.state.title} <span style={{textTransform:'capitalize'}}>{"(" + this.state.type}</span> - {this.state.year + ")"}</h3>
</div>
<div className="flex-padding-right">
Rating
</div>
</div>
<div className="row">
<div className="col-xs-12">
<p className="text--subtext">{this.state.runtime} | {this.state.genre} | {this.state.released}</p>
</div>
</div>
<div className="row">
<div className="col-xs-12 mt-small">
{this.state.plot}
</div>
</div>
<div className="row">
<div className="col-xs-12 col-sm-6">
<ul className="list">
<li>Director: {this.state.director}</li>
<li>Writer: {this.state.writer}</li>
<li>Language: {this.state.language}</li>
<li>Actors: {this.state.actors}</li>
</ul>
</div>
</div>
<div className="row">
<div className="col-xs-12 box">
List of ratings
</div>
</div>
<div className="row">
<div className="col-xs-12 box">
Awards
</div>
</div>
</div>
</div>
</div>
{/* EINDE MOVIE COMPONENT */}
</div>
</div>
</div>
);
// EINDE RETURN
}
}
isEmpty(variable){return (variable == "" || variable === "" || variable == undefined || variable === undefined || variable == null || variable === null) == true;}
}
export default App;
If there's anything unclear, let me know and I will update my post when I have time.

Categories