React MovieDB API problem. Setting this.setState twice breaks my component - ajax

I followed a youtube tutorial using the MovieDB API with React. I can search for movies and pull up a title, description, and movie image. I want to add the ability for a youtube trailer to be played. I have successfully fetched the youtube ID's and store them. I'm having trouble displaying them. If I uncomment in my App.js
this.setState({ rows: videoRows });
Then the youtube videos work. But my title, description, and movie image are undefined and vice versa.
App.js
import React, { Component } from "react";
import "./App.css";
import MovieRow from "./MovieRow.js";
import $ from "jquery";
class App extends Component {
constructor(props) {
super(props);
this.state = {};
this.performSearch("woman");
}
performSearch(searchTerm) {
// console.log("perform search");
const API_KEY = "625914798003ef54176364f32c232968";
const urlString = `https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${searchTerm}`;
const urlVideoString = `https://api.themoviedb.org/3/movie/297762/videos?api_key=${API_KEY}&language=en-US`;
//fetch generic info
$.ajax({
url: urlString,
success: searchResults => {
console.log("fetch basic success");
const results = searchResults.results;
var videoRows = [];
var movieRows = [];
// call next ajax function
$.ajax({
url: urlVideoString,
success: searchResults => {
console.log("fetch Youtube video key success");
const results = searchResults.results;
results.forEach(movie => {
movie.video_src = movie.key;
console.log(movie.video_src);
var videoRow = <MovieRow key={movie.id} movie={movie} />;
videoRows.push(videoRow);
});
//If I run this line below it will break
//my generic basic info(title, movie description, and picture) ,
//but it makes the youtube player work
// this.setState({ rows: videoRows });
},
error: (xhr, status, err) => {
console.log("failed video fetch");
}
});
results.forEach(movie => {
movie.poster_src =
"https://image.tmdb.org/t/p/w185" + movie.poster_path;
console.log(movie.poster_path);
const movieRow = <MovieRow key={movie.id} movie={movie} />;
movieRows.push(movieRow);
});
this.setState({ rows: movieRows });
},
error: (xhr, status, err) => {
console.log("failed fetch");
}
});
}
searchChangeHandler(event) {
console.log(event.target.value);
const boundObject = this;
const searchTerm = event.target.value;
boundObject.performSearch(searchTerm);
}
render() {
return (
<div>
<table className="titleBar">
<tbody>
<tr>
<td>
<img alt="app icon" width="100" src="green_app_icon.svg" />
</td>
<td width="8" />
<td>
<h1>MoviesDB Search</h1>
</td>
</tr>
</tbody>
</table>
<input
style={{
fontSize: 24,
display: "block",
width: "99%",
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 16
}}
onChange={this.searchChangeHandler.bind(this)}
placeholder="Search for movie by title..."
/>
{this.state.rows}
</div>
);
}
}
export default App;
MovieRow.js
import React from "react";
import YouTube from "react-youtube";
class MovieRow extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
viewMovie() {
const url = "https://www.themoviedb.org/movie/" + this.props.movie.id;
window.location.href = url;
}
viewTrailer() {
//const trailerURL = "https://www.youtube.com/watch?v=1Q8fG0TtVAY";
}
_onReady(event) {
// access to player in all event handlers via event.target
event.target.pauseVideo();
}
render() {
const opts = {
height: "390",
width: "50%",
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 1
}
};
return (
<table key={this.props.movie.id}>
<tbody>
<tr>
<td>
<img alt="poster" width="180" src={this.props.movie.poster_src} />
</td>
<td>
<h1>src {this.props.movie.video_src}</h1>
<h3>{this.props.movie.title}</h3>
<p>{this.props.movie.overview}</p>
<YouTube
videoId={this.props.movie.video_src}
opts={opts}
onReady={this._onReady}
/>
<input
className="btn btn-primary"
type="button"
onClick={this.viewTrailer.bind(this)}
value="Play Trailer"
/>
<input
className="btn btn-primary"
type="button"
onClick={this.viewMovie.bind(this)}
value="View"
/>
</td>
</tr>
</tbody>
</table>
);
}
}
export default MovieRow;

I see a lot of problems going on here. One is that row state is not initialized.
You should always initialize a state on the constructor
this.state = {
row: []
};
The other problem I saw is that you shouldn't be storing components in state.
See this
State should contain data that a component's event handlers may change to trigger a UI update. In real apps this data tends to be very small and JSON-serializable. When building a stateful component, think about the minimal possible representation of its state, and only store those properties in this.state. Inside of render() simply compute any other information you need based on this state. You'll find that thinking about and writing applications in this way tends to lead to the most correct application, since adding redundant or computed values to state means that you need to explicitly keep them in sync rather than rely on React computing them for you.
What you can do instead is rewriting your state like
this.state = {
movies: []
}
And inside your ajax request
// Change these lines to
results.forEach(movie => {
movie.video_src = movie.key;
console.log(movie.video_src);
var videoRow = <MovieRow key={movie.id} movie={movie} />;
videoRows.push(videoRow);
});
// To this
results.forEach(movie => {
movie.video_src = movie.key;
videoRows.push(movie);
});
this.setState({ movies: videoRows });
And lastly inside your render method, loop all the movies in your state and and return
your MovieRow component
{this.state.movies.map(movie => <MovieRow key={movie.id} movie={movie}/>)}

Related

Show component only after all images loaded

I am using Vue 3 and what i would like to achieve is to load all images inside a card (Album Card) and only then show the component on screen..
below is an how it looks now and also my code.
Does anybody have an idea how to achieve this?
currently component is shown first and then the images are loaded, which does not seem like a perfect user experience.
example
<template>
<div class="content-container">
<div v-if="isLoading" style="width: 100%">LOADING</div>
<album-card
v-for="album in this.albums"
:key="album.id"
:albumTitle="album.title"
:albumId="album.id"
:albumPhotos="album.thumbnailPhotos.map((photo) => photo)"
></album-card>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import albumCard from "#/components/AlbumCard.vue";
interface Album {
userId: number;
id: number;
title: string;
thumbnailPhotos: Array<Photo>;
}
interface Photo {
albumId: number;
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
export default defineComponent({
name: "Albums",
components: {
albumCard,
},
data() {
return {
albums: [] as Album[],
isLoading: false as Boolean,
};
},
methods: {
async getAlbums() {
this.isLoading = true;
let id_param = this.$route.params.id;
fetch(
`https://jsonplaceholder.typicode.com/albums/${
id_param === undefined ? "" : "?userId=" + id_param
}`
)
.then((response) => response.json())
.then((response: Album[]) => {
//api returns array, loop needed
response.forEach((album: Album) => {
this.getRandomPhotos(album.id).then((response: Photo[]) => {
album.thumbnailPhotos = response;
this.albums.push(album);
});
});
})
.then(() => {
this.isLoading = false;
});
},
getRandomPhotos(albumId: number): Promise<Photo[]> {
var promise = fetch(
`https://jsonplaceholder.typicode.com/photos?albumId=${albumId}`
)
.then((response) => response.json())
.then((response: Photo[]) => {
const shuffled = this.shuffleArray(response);
return shuffled.splice(0, 3);
});
return promise;
},
/*
Durstenfeld shuffle by stackoverflow answer:
https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array/12646864#12646864
*/
shuffleArray(array: Photo[]): Photo[] {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
},
},
created: function () {
this.getAlbums();
},
});
</script>
What i did to solve this problem was using function on load event on (img) html tag inside album-card component. While images are loading a loading spinner is shown. After three images are loaded show the component on screen.
<template>
<router-link
class="router-link"
#click="selectAlbum(albumsId)"
:to="{ name: 'Photos', params: { albumId: albumsId } }"
>
<div class="album-card-container" v-show="this.numLoaded == 3">
<div class="photos-container">
<img
v-for="photo in this.thumbnailPhotos()"
:key="photo.id"
:src="photo.thumbnailUrl"
#load="loaded()"
/>
</div>
<span>
{{ albumTitle }}
</span>
</div>
<div v-if="this.numLoaded != 3" class="album-card-container">
<the-loader></the-loader>
</div>
</router-link>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { store } from "#/store";
export default defineComponent({
name: "album-card",
props: {
albumTitle: String,
albumsId: Number,
},
data: function () {
return {
store: store,
numLoaded: 0,
};
},
methods: {
thumbnailPhotos() {
return this.$attrs.albumPhotos;
},
selectAlbum(value: string) {
this.store.selectedAlbum = value;
},
loaded() {
this.numLoaded = this.numLoaded + 1;
},
},
});
</script>
Important note on using this approach is to use v-show instead of v-if on the div. v-show puts element in html(and sets display:none), while the v-if does not render element in html so images are never loaded.

How to manipulate axios returned data to print in desired formate

<template>
<div>
<table class="table table-responsive">
<tbody>
<tr v-for="(gameresults, index) in gameresults" :key="index">
<td style="color: #082ad4; font-size: 24px;">{{ gameresults.game_name }}</br>
<h3 style="color:#00d2f1; font-size: 18px;">{{ gameresults.cards }}</h3></td>
<h3 style="color:#00d2f1; font-size: 18px;">{{ this.formattedArray }}</h3></td>
</tr>
</tbody>
</table>
</div></template>
<script>
export default {
props: [''],
mounted() {
console.log('Component mounted.')
},
data() {
return {
gameresults:0,
};
},
methods: {
changeResult() {
let formattedArray = [];
this.gameresults.cards.forEach(str => {
const subs = str.split('');
const subsTwo = subs[2].split(',');
const formattedString = `${subs[1]} - ${subs[0]}-${subsTwo[0]}${subs[4]}-${subsTwo[1]}`;
formattedArray.push(formattedString);
});
console.log('Formatted Data', formattedArray);
}
// this.gameresults[0].cards
},
computed: {
chkgameresults() {
axios.post('/gameresults')
.then(response => {
this.gameresults = response.data ;
this.changeResult();
});
},
},
created () {
this.chkgameresults();
}
};
</script>
ref code axios fetches mysql concat data in array format having 2 keys [game_name and card ] i want card key to be manipulated . when i print card array using this.gameresults[0].card its giving me 123-9,897-0 using {{ gameresults.cards }} inside vu template , i want value to get manipulated like 123-90-897 ( only last 0 gets before the second exp and become 0-897 removing ',' separator
Assuming that your data is an array lets define a method...........
formatData(myData) {
let formattedArray = [];
myData.myValue.forEach(str => {
const subs = str.split('');
const subsTwo = subs[2].split(',');
const formattedString = `${subs[1]} - ${subs[0]}-${subsTwo[0]}${subs[4]}-${subsTwo[1]}`;
formattedArray.push(formattedString);
});
console.log('Formatted Data', formattedArray);
}

How can I force order of fetch result processing in my React app?

I'm using React 16.13.0. I want to create a simple search component -- a single text box that as you type displays results. I have created the following component ...
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: "",
setSearchTerm: "",
searchResults: [],
setSearchResults: []
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const query = event.target.value;
if ( ! query ) {
this.setState({ searchTerm: query, searchResults: [] } );
} else {
this.setState({ searchTerm: query, loading: true }, () => {
this.doSearch(query);
});
}
}
doSearch = ( query ) => {
console.log("dosearch:" + query);
const searchUrl = '/coops/?contains=' + encodeURIComponent(query);
fetch(searchUrl,{
method: "GET"
}).then(response => response.json())
.then(data => {
console.log("query:" + query);
console.log(data);
this.setState({
searchResults: data,
loading: false,
});
});
};
renderSearchResults = () => {
const {searchResults} = this.state;
if (searchResults && searchResults.length) {
return (
<div className="results-container">
<div>Results</div>
<ul>
{searchResults.map(item => (
<li className="result-items" key={item.id} value={item.name}>{item.name}</li>
))}
</ul>
</div>
);
}
};
render() {
return (
<div className="searchForm">
<input
type="text"
placeholder="Search"
value={this.state.searchTerm}
onChange={this.handleChange}
/>
{ this.renderSearchResults() }
</div>
);
}
The issue is that if I type too fast, the fetch requests do not necessarily complete in the order they are sent out. For exmple, if my term is "south," the fetch corresponding to having typed "sou" may complete after I've fully typed "south," causing the results to be displayed incorrectly. How do I account for this and force my results to be displayed corresponding to the inputs typed?
You need to use onKeyUp={} this means that when user finished typing their search query only there you will start making the request.
<input
type="text"
placeholder="Search"
value={this.state.searchTerm}
onKeyUp={this.handleChange}
/>

Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component

Homepage.js
import React, { Component } from 'react';
import { Route, Redirect, withRouter } from 'react-router-dom';
import $ from 'jquery';
import { css } from 'glamor';
import { ToastContainer } from 'react-toastify';
import toast from '../toast';
import { BarLoader } from 'react-spinners';
// ---------------- Custom components
import Header from '../Header/Header';
import Footer from '../Footer/Footer';
import RelayAnimation from '../RelayAnimation/RelayAnimation';
import UserLoginForm from '../UserLoginForm/UserLoginForm';
import UserSignUpForm from '../UserSignUpForm/UserSignUpForm';
import PassResetReqForm from '../PassResetReqForm/PassResetReqForm';
import PassResetForm from '../PassResetForm/PassResetForm';
import './HomePage.css';
// --------- Message for Network Error
const Msg = () => (
<div>
Error please, try again later <br /> or reload the Page.
</div>
);
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
this.toggleLoader = this.toggleLoader.bind(this);
this.notifySuccess = this.notifySuccess.bind(this);
this.notifyError = this.notifyError.bind(this);
}
notifySuccess(msg) {
toast.success(msg);
}
notifyError(msg) {
toast.error(msg);
}
// --------- Toast Notifications ---------------
// notify = (status) => {
// // --------- Server Issue Toaster
// if (status === 'Bad Gateway') {
// toast.error(<Msg />, {
// className: {
// color: '#fff',
// minHeight: '60px',
// borderRadius: '8px',
// boxShadow: '2px 2px 20px 2px rgba(0,0,0,0.3)'
// }
// });
// }
// }
toggleLoader() {
this.setState({
loading: !this.state.loading
});
}
isAuthenticated() {
const token = localStorage.getItem('authToken');
if (token) {
return true;
}
}
componentDidMount() {
const currentLocationPath = this.props.location.pathname;
const urlForEmailVerification = currentLocationPath.includes('/api/v1/verifyEmailUser/');
if (urlForEmailVerification) {
const { token } = this.props.match.params; // token value from url params passed by <Route/>
if (token) {
const url = `/api/v1/verifyEmailUser/${token}`;
// api call to make the user's account verified in db based on token in url
$.ajax({
url: url,
dataType: 'json',
type: 'GET',
success: function (res) {
console.log(res);
this.notifySuccess('emailVerified');
this.props.history.push('/');
}.bind(this),
error: function (xhr, status, err) {
console.error(url, status, err.toString());
}.bind(this)
});
}
}
}
render() {
const currentLocationPath = this.props.location.pathname;
const isAuthenticated = this.isAuthenticated();
const resetPasswordPathname = currentLocationPath.includes('/api/v1/resetPassword/');
if (!isAuthenticated) {
return (
<div className="App d-flex flex-column">
{/* Navbar with brand logo and language change dropdown and signup/login button */}
< Header />
{/* Main Section with RelayStream Animation graphic and forms */}
<div className="container py-4 py-md-0 pt-lg-4 d-flex flex-grow" >
<div className={'LoginScreen d-flex align-items-center align-items-lg-start ' +
((currentLocationPath === '/login' ||
currentLocationPath === '/signup' ||
currentLocationPath === '/forgot-password' ||
resetPasswordPathname) ? 'justify-content-around' : 'justify-content-center')}>
{/* RelayStream Animation graphic */}
<RelayAnimation />
{/* forms to switch between based on path change by <Router/> */}
<Route path="/login" component={(props) => <UserLoginForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
<Route path="/signup" component={(props) => <UserSignUpForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
<Route path="/forgot-password" component={(props) => <PassResetReqForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
<Route path="/api/v1/resetPassword/:token" component={(props) => <PassResetForm {...props} notifySuccess={this.notifySuccess} notifyError={this.notifyError} toggleLoader={this.toggleLoader} />} />
</div>
</div >
{/* Footer with copyright message */}
<Footer />
<div className={this.state.loading ? 'loader flex-column' : 'd-none'}>
<span className="loader__title">Loading...</span>
<BarLoader color={'#36D7B7'} loading={this.state.loading} />
</div>
{/* React toastify for toast notification */}
<ToastContainer className={{ textAlign: 'center' }} progressClassName={css({ background: '#007aff' })} />
</div >
);
} else {
return <Redirect to={'/dashboard'} />;
}
}
}
export default withRouter(HomePage);
UserLoginForm.js
import React, { Component } from 'react';
import { Link, Redirect } from 'react-router-dom';
import $ from 'jquery';
import { Animated } from 'react-animated-css';
import SocialButton from '../SocialButton/SocialButton';
// ---------------- Form components
import Form from 'react-validation/build/form';
import Button from 'react-validation/build/button';
// ---------------- Custom Form components & validations
import { Email, Password, required, noSpace, minChar8, email } from '../formValidation';
import FontAwesomeIcon from '#fortawesome/react-fontawesome';
import facebook from '#fortawesome/fontawesome-free-brands/faFacebookF';
import google from '#fortawesome/fontawesome-free-brands/faGooglePlusG';
import './UserLoginForm.css';
class UserLoginForm extends Component {
constructor(props) {
super(props);
this.state = {
fireRedirect: false
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSocialLogin = (user) => {
const provider = user._provider;
const name = user._profile.name;
const email = user._profile.email;
const profilePic = user._profile.profilePicURL;
const token = user._token.accessToken;
const data = { provider, name, email, profilePic, token };
console.log(data);
const url = '/api/v1/loginWithFacekbook'; // social login's api url
// api call for social logins
$.ajax({
url: url,
dataType: 'json',
type: 'POST',
data: data,
success: function (res) {
console.log('success response after api call ===>>', res);
const generatingAuthToken = res.object.generatingAuthToken;
const apikey = generatingAuthToken.apiKey;
const authToken = generatingAuthToken.authToken;
localStorage.setItem('apiKey', apikey);
localStorage.setItem('authToken', authToken);
// if social login was successful then redirect user to dashboard
this.setState({ fireRedirect: true });
}.bind(this),
error: function (xhr, status, err) {
console.log(status);
// if there was network issue notify user to try again later or refresh page
this.props.notifyError(err.toString());
console.error(url, status, err.toString());
}.bind(this)
});
}
handleSocialLoginFailure = (err) => {
console.error(err)
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
// input field animation code - adds class to current focused input field's parent
if (value) {
target.parentElement.classList.add('input--filled');
} else {
target.parentElement.classList.remove('input--filled');
}
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
// show loading spinner
this.props.toggleLoader();
// get data from all field in form
const data = this.form.getValues();
const url = '/api/v1/loginUser'; // user login api url
// api call to generate token and apikey and login user to dashboard
$.ajax({
url: url,
dataType: 'json',
type: 'POST',
data: data,
success: function (res) {
console.log('success response after api call ===>>', res);
const obj = res.object;
const loginStatus = res.status;
const msg = res.message;
// check if authToken and apiKey was received
if (obj) {
// if data is found in database check if credentials provided were correct
if (loginStatus) {
JSON.stringify(obj);
// save apiKey and token in loacalStorage
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let val = obj[key];
localStorage.setItem(key, val);
}
}
// turn off loader spinner
this.props.toggleLoader();
this.setState({ fireRedirect: true });
// if credentials were correct accept login then redirect user to dashboard
} else { // if credentials were wrong
// turn off loader spinner
// this.props.toggleLoader();
// then notify about wrong credentials
this.props.notifyError(msg);
}
} else { // if data was not found in database notify user to signup first
this.props.notifyError(msg);
// turn off loader spinner
// this.props.toggleLoader();
}
}.bind(this),
error: function (xhr, status, err) {
console.log(status);
// if there was network issue notify user to try again later or refresh page
this.props.notify(err.toString());
console.error(url, status, err.toString());
}.bind(this)
});
}
render() {
const { fireRedirect } = this.state;
return (
<Animated className="form-animation" animationIn="fadeInRight" animationOut="fadeOutLeft" isVisible={true}>
<Form className="userLoginForm" ref={c => { this.form = c }} onSubmit={this.handleSubmit} noValidate>
<h2 className="formTitle text-center">LOG IN</h2>
<Email
required
pattern="[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,3}$"
id="email"
name="email"
type="email"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[required, email]} />
{/* must wrap Password Field Component with "div.form-group" */}
<div className="form-group">
<Password
required
id="password"
name="password"
type="password"
minLength="8"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[noSpace, required, minChar8]} />
{/* Optional Link Component below...
Note: If there is no requirement of Link or any Other Element just below <Password/> input field
in entire project then the wrapping "div.form-group" can also be put inside
Password Component's Template Located in formValidation.js file*/}
<Link to="/forgot-password" className="float-right forgotPassword">
<small>Forgot password ?</small>
</Link>
</div>
<div className="form-group submitGroup text-center">
<Button type="submit" className="btn btn--submit btn-rounded btn-outline-primary mx-auto">LOG IN</Button>
</div>
{/* login buttons for facebook and google login */}
<div className="socialLogin mx-auto">
{/* <a href="#" className="socialBtn socialBtn--facebook rounded-circle">
<FontAwesomeIcon icon={facebook} />
</a> */}
<SocialButton
className="socialBtn socialBtn--facebook rounded-circle"
provider='facebook' appId='873380466175223'
onLoginSuccess={this.handleSocialLogin}
onLoginFailure={this.handleSocialLoginFailure}
redirect="/dashboard">
<FontAwesomeIcon icon={facebook} />
</SocialButton>
<span className="seperator" />
<SocialButton
className="socialBtn socialBtn--googlePlus rounded-circle"
provider="google"
appId="843586925977-d7j31p5j0me5kqvcp29nr9s37reg5b5u.apps.googleusercontent.com"
onLoginSuccess={this.handleSocialLogin}
onLoginFailure={this.handleSocialLoginFailure}>
<FontAwesomeIcon icon={google} />
</SocialButton>
{/* <a href="#" className="socialBtn socialBtn--googlePlus rounded-circle">
<FontAwesomeIcon icon={google} />
</a> */}
</div>
{/* code to redirect user to dashboard page after successful login */}
{fireRedirect && (<Redirect to={'/dashboard'} />)}
</Form>
</Animated>
);
}
}
export default UserLoginForm;
UserSignUpForm.js
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import $ from 'jquery';
import { Animated } from 'react-animated-css';
// ---------------- Form components
import Form from 'react-validation/build/form';
import Button from 'react-validation/build/button';
// ---------------- Custom Form components & validations
import { UserName, Email, NewPassword, ConfirmPassword, noSpace, required, minChar8, email, confirmPassword } from '../formValidation';
import './UserSignUp.css';
class UserSignUpForm extends Component {
constructor(props) {
super(props);
this.state = {
userName: '',
email: '',
password: '',
confirmPassword: '',
fireRedirect: false
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
// input field animation code - adds class to current focused input field's parent
if (value) {
target.parentElement.classList.add('input--filled');
} else {
target.parentElement.classList.remove('input--filled');
}
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
const data = this.form.getValues();
console.log(data);
// api call to sign up user
$.ajax({
url: '/api/v1/user',
dataType: 'json',
type: 'POST',
data: data,
success: function (res) {
console.log('success response after api call ===>>', res);
const signUpStatus = res.status;
const msg = res.message;
if (signUpStatus) {
// if signup was successful notify user
this.props.notifySuccess(msg);
// redirect user to login form
this.setState({ fireRedirect: true });
} else {
this.props.notifyError(msg); // notify user on signup fail
}
}.bind(this),
error: function (xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}
render() {
const { fireRedirect } = this.state;
return (
<Animated className="form-animation" animationIn="fadeInRight" animationOut="fadeOutLeft" isVisible={true}>
<Form className="userSignUpForm" ref={c => { this.form = c }} onSubmit={this.handleSubmit}>
<h2 className="formTitle text-center">SIGN UP</h2>
<UserName
required
id="userName"
name="userName"
ref={c => { this.UserName = c }}
value={this.state.userName}
type="text"
className="input__field input__field--madoka"
validations={[required]}
onChange={this.handleInputChange} />
<Email
required
pattern="[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,3}$"
id="email"
name="email"
ref={c => { this.Email = c }}
value={this.state.email}
type="email"
className="input__field input__field--madoka"
validations={[required, email]}
onChange={this.handleInputChange} />
<div className="form-group">
<NewPassword
required
id="password"
name="password"
ref={c => { this.NewPassword = c }}
value={this.state.password}
type="password"
minLength="8"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[noSpace, required, minChar8]} />
</div>
<div className="form-group">
<ConfirmPassword
required
id="confirmPassword"
name="confirmPassword"
ref={c => { this.ConfirmPassword = c }}
value={this.state.confirmPassword}
type="password"
minLength="8"
className="input__field input__field--madoka"
onChange={this.handleInputChange}
validations={[noSpace, required, confirmPassword]} />
</div>
<div className="form-group submitGroup text-center">
<Button type="submit" className="btn btn--submit btn-rounded btn-outline-primary mx-auto">SIGN UP</Button>
</div>
{/* code to redirect user to dashboard page after successful login */}
{fireRedirect && (<Redirect to={'/login'} />)}
</Form>
</Animated>
);
}
}
export default UserSignUpForm;
i am facing many issues. first i have a spinner in homepage.js which shows as overlay on whole page when this.state.loading = true, initially it is set to False in constructor.
Issues 1.) when we submit the login form (if signup was done already ) the loader shows for fraction of second on screen but " this.setState({ fireRedirect: true }); " this code is supposed to redirect user to dashboard which it does but gives error in console :
index.js:2178 Warning: Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
index.js:2178 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the UserLoginForm component.
Issue 2.) If i create a new user from UserSignUpForm which redirects us to UserLoginForm. now if i try to login i get only this error:
index.js:2178 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the UserLoginForm component.
Issue 3.) If i remove or comment "this.toggleLoader();" from the ajax's success functions right before " this.setState({ fireRedirect: true }); " this line. then the loader shows on screen but doesn't go away even page doesn't redirect and console gives this error:
index.js:2178 Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the UserLoginForm component.

Input search filter not working (React)

Have component that works with React, Redux and AJAX:
class IncomeProfile extends Component {
constructor(props) {
super(props)
this.state = {
items: this.props.items || []
}
}
componentDidMount() {
this.props.IncomeListProfile();
}
componentWillReceiveProps(nextProps) {
this.setState({ items: nextProps.items });
}
filterList(event) {
var updatedList = this.state.items;
updatedList = updatedList.filter(function(item) {
return item.toLowerCase().search(event.target.value.toLowerCase()) !== -1;
});
this.setState({items: updatedList}); // now this.state.items has a value
}
render() {
var elems = this.props.items;
if (typeof elems == 'undefined') {
elems = [];
}
//console.log(elems);
return (
<div>
<table className='table'>
<thead className='theadstyle'>
<th>date
<input></input>
</th>
<th>
<input onChange={this.filterList} type='text' placeholder='keyword search'></input>
</th>
<th>
<input type='text' placeholder='amount search'></input>
</th>
<th>amount</th>
<th>Show archived</th>
</thead>
<div className='tbodymar'></div>
<tbody >
{elems.map((item) => (
<tr key={item.course_id}>
<td>{item.created_at}</td>
<td>{item.name}</td>
<td>{item.remark}</td>
<td>{item.income_amount}</td>
<td>more options</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList())
}
}
const mapStateToProps = function(state) {
//var mystore = state.toJS()
var mystore = state.getIn(['incomeProfileList'])['course_list'];
//console.log(mystored.hh);
var copy = Object.assign({}, mystore);
return {items: copy.course_list};
}
export default connect(mapStateToProps, mapDispatchToProps)(IncomeProfile);
When I enter something in input, I get error Cannot read property 'state' of undefined, but console.log show's my state. What wrong with filter ? Where mistake? if I had right state?
this.props.items only get populate after componentDidMount?
If so, and you want to set the items in the state too. You can use componentWillReceiveProps method to set the new props to you state.
componentWillReceiveProps(nextProps) {
this.setState({ items: nextProps.items });
}

Resources