React function being called multiple times(every second) - javascript

I am using React and rendering a modal from Material UI. The way the modal is rendered is it has to be a part of the body of code. So I added it to the bottom of the page. The state determines whether or not the modal is open. The issue is that I can see that a function that is in the modal is being called multiple times. And very rapidly. Like more than once each second. Please see my code:
class ComponentName extends React.Component {
constructor(props) {
super(props);
this.state = {
countries: [],
isButtonDisabled: false,
formError: false,
showModalCta: false,
showModal: false,
iframeUrl: ''
};
}
handleClose = () => {
this.setState({
showModal: false
});
};
handleShowModal = () => {
this.setState({
showModal: true
})
}
showModalContent = () => {
const { classes } = this.props;
const { iframeUrl } = this.state;
getiframeUrl().then((res) => {
this.setState({
iframeUrl: res.level2VerificationUrl
});
});
return (
<Paper className={classes.modalPaper}>
<iframe src={iframeUrl} width="500px" height="500px" />
</Paper>
);
};
render() {
const {
classes, history, firstName, lastName, country, region, address, city, dob, phone, smsCode, postalCode, actions
} = this.props;
const {
countries, formError, isButtonDisabled, showCta, showModal
} = this.state;
return (
<div>
<Modal className={classes.modal} open={showModal} onClose={this.handleClose}>
{this.showModalContent()}
</Modal>
</div>
);
}
}
Its pretty much calling that function to get the url every second. But I dont quite understand why this is the behavior. Been doing research on this but no answers. Is there any way to prevent this? Thanks!

showModalContent will be executed on every "state change" of the component (on every render).
There (from what I see) you are making a call to a promise (getiframeUrl) and you are setting the state of the component (which makes it change state).
Hence: Render -> showModalContent -> change state -> re-render -> showModalContent -> ... (infinite loop).
My advice is that you do the setState of iframeUrl only in the componentDidMount. Something like this:
componentDidMount() {
const { iframeUrl } = this.state;
getiframeUrl().then((res) => {
this.setState({
iframeUrl: res.level2VerificationUrl
});
});
}
showModalContent = () => {
const { classes } = this.props;
return (
<Paper className={classes.modalPaper}>
<iframe src={iframeUrl} width="500px" height="500px" />
</Paper>
);
};

Related

Storing both search and searched term

So I have stored the current search term in a state using e.target.value, i titled it searchField but when I clear the input value, i cannot use it anymore. I am trying to post a message in the body telling the user that their specific searched term provided no results but i cleared the searchField state. So i need to create another state for searchedTerm in addition to my existing "searchField but unsure how to do it . Feel like I am overlooking something quite simple
On click/submit, can you take the state of searchField and add it into another state (searchedTerm) before setting the searchField to ''
import React, { Component } from 'react';
import './App.css';
import request from 'superagent';
import BookList from './BookList'
import Search from './Search'
class App extends Component {
constructor(props) {
super(props);
this.state = {
bookList: [],
hasError: false,
searchField: '',
value:'',
loading: false,
searchResult: ''
};
}
handleSearch = (e) => {
this.setState ({
searchField: e.target.value
})
}
handleClickBookLookup = (e) => {
this.setState({
hasError: false,
bookList: [],
loading: true
})
request
.get('https://www.googleapis.com/books/v1/volumes/')
.query({q: this.state.searchField})
.then ((data) => {
this.setState({
bookList: [...data.body.items],
loading: false,
searchField: ''
})
})
.catch(() => {
this.setState({
hasError: true ,
loading: false,
searchField: ''
});
});
}
handleSubmit = (e) => {
e.preventDefault()
}
render() {
return (
<div className="App">
<Search
handleSearch = {this.handleSearch}
handleSubmit = {this.handleSubmit}
handleClickBookLookup = {this.handleClickBookLookup}
searchField = {this.state.searchField}
/>
<h1>List Of Books</h1>
{this.state.hasError && (
<p>no books found for '________'.</p>
)}
<ul>
{this.state.loading && (
<p>Loading</p>
)}
{this.state.bookList.map((book, i) => {
return (
<BookList
book={book}
/>
)
})}
</ul>
</div>
);
}
}
export default App;
If you want to show the message to the user, you dont have to clear the state.
However if you want to clear it and still have to show the message. You can do that with a timeout. But since you want to clear the state, message should disappear after some time. You dont to want to keep different states for the same thing, it is ambiguous.
You can add a setTimeout in then or catch depending on your case. Clear the state after you have shown the message.
setTimeout(() => { this.setState({ hasError: false, searchField: '' }) },3000)
I think I get what you mean now. What you should do is copy the search value into a local instance variable of the component. That way, even if you reset the state, their search term is still accessible
I've added comments for the changes I've made:
import React, { Component } from 'react';
import './App.css';
import request from 'superagent';
import BookList from './BookList'
import Search from './Search'
class App extends Component {
constructor(props) {
super(props);
// Keep a local instance variable with their search term in it
this._searchField = '';
this.state = {
bookList: [],
hasError: false,
searchField: '',
value:'',
loading: false,
searchResult: ''
};
}
handleSearch = (e) => {
this.setState ({
searchField: e.target.value
});
}
handleClickBookLookup = (e) => {
// Update the local variable here
this._searchField = this.state.searchField;
this.setState({
hasError: false,
bookList: [],
loading: true
})
request
.get('https://www.googleapis.com/books/v1/volumes/')
.query({q: this.state.searchField})
.then ((data) => {
// In here we only reset the state value, NOT the local instance value.
// This means what they searched is still accessible
this.setState({
bookList: [...data.body.items],
loading: false,
searchField: ''
})
})
.catch(() => {
this.setState({
hasError: true ,
loading: false,
searchField: ''
});
});
}
handleSubmit = (e) => {
e.preventDefault()
}
render() {
return (
<div className="App">
<Search
handleSearch = {this.handleSearch}
handleSubmit = {this.handleSubmit}
handleClickBookLookup = {this.handleClickBookLookup}
searchField = {this.state.searchField}
/>
<h1>List Of Books</h1>
{this.state.hasError && (
// Here, we render the local instance variable
<p>no books found for {this._searchField}.</p>
)}
<ul>
{this.state.loading && (
<p>Loading</p>
)}
{this.state.bookList.map((book, i) => {
return (
<BookList
book={book}
/>
)
})}
</ul>
</div>
);
}
}
export default App;
Of course be aware that changing _searchField won't trigger a re-render, since it's not in the state. Doesn't matter in this use case, but just something to remember.
As a side note, in terms of UX, be careful completely wiping someone's search term(s) when they search. Means if they had a small typo, or want to make a small change to their search like adding or removing a word or changing some spelling, they gotta type it out all over again. It's often better to preserve their search, and give an option to clear it, like how many search boxes have a little cross icon on the right hand side that wipes the search box

React: Pass 2 props back to the parent component in the same function from the child

I want to set the state and close the modal at the same time, both are done by passing props back to the parent component. The problem I'm having is that only 1 seems to want to work at a time. I can get both working by themselves, but as soon as they're both there it doesn't work. How can I solve this?
CHILD COMPONENT
useSelectedImage = () => {
this.props.saveChosenImage(this.state.imageChosen)
this.props.closeModal();
};
<button onClick={this.useSelectedImage}>INSERT IMAGE</button>
PARENT COMPONENT
state = {
imageModalOpen: false,
}
// open the image modal
openImageModal = () => {
this.setState({ ...this.state, imageModalOpen: true })
};
// close the image modal
handleImageClose = () => {
this.setState({ ...this.state, imageModalOpen: false })
};
<Modal
open={this.state.imageModalOpen}
onClose={this.handleImageClose}
>
<DialogContent className={classes.DialogContent}>
<SelectImageModal saveChosenImage={this.saveChosenImage} closeModal={this.handleImageClose}/>
</DialogContent>
<modal>
saveChosenImage = (image) => {
this.setState({ ...this.state, imageChosen: image })
};
try like this.
CHILD COMPONENT
useSelectedImage = () => {
this.props.saveChosenImage(this.state.imageChosen);
- this.props.closeModal(); //remove this
};
...
PARENT COMPONENT
...
saveChosenImage = (image) => {
this.setState({ ...this.state, imageChosen: image, imageModalOpen: false }); // modified here
};
You could call one function inside the other. This way you maintain the purity of your methods. You could still reuse handleImageClose on a close button without selecting an image.
See the example on codesandbox https://codesandbox.io/s/sweet-torvalds-3dyv5
import React, { Component } from "react";
import ReactDOM from "react-dom";
const Child = props => {
const useSelectedImage = () => {
console.log("clicked");
// DO THIS
return props.closeModal(props.saveChosenImage(props.theImage));
};
return (
<div>
<button onClick={useSelectedImage}>Select Image</button>
<button onClick={props.closeModal}>Cancel</button>
</div>
);
};
class Parent extends Component {
openImageModal = () => console.log("OPEN FIRED");
handleImageClose = () => {
console.log("CLOSED FIRED");
this.setState({ closed: true });
};
saveChosenImage = image => {
console.log(`SAVED!! ${image}`);
this.setState({ image });
};
render() {
console.log(this.state);
return (
<Child
saveChosenImage={this.saveChosenImage}
closeModal={this.handleImageClose}
theImage="cow.jpg"
/>
);
}
}
function App() {
return (
<div className="App">
<Parent />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Refresh a specific component 's data/ state in React

I have a List of products-ID and a button. When I press the button, I want to refresh the data in the ListComponent. I have no idea how can I do this in React. Can someone help me?
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state); //function that fetches-takes all products
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
};
};
Your refresh function needs to call an action that fetches the data, and updates the Redux store accordingly. And because you've mapped part of your Redux state to this component's props, it will re-render when that data is fetched and saved via the reducer.
Therefore, you don't need to set local state at all in this component. Provided you have an action called fetchProductData:
class ProductList extends React.Component {
constructor (props) {
super(props)
this.refresh = this.refresh.bind(this)
}
// if you don't already have the data in your store, you can fetch it here to kick things off
componentDidMount () {
this.props.fetchProductData()
}
refresh () {
this.props.fetchProductData()
}
render () {
const { products } = this.state
return (
<div>
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state)
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
}
}
export default connect(mapStateToProps, { fetchProductData })(MyComponent)
Again, this assumes that fetchProductData dispatches an action that will update the redux state where products are stored. Passing the action to connect like this will make it available as a prop within the component.
It looks like you've placed your refresh() inside the constructor, try:
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
I made a minimal component that does what you want it to do. Instead of binding in the constructor i use a fat arrow function for refresh.
import { Component } from "react";
const ListItem = props => props.item.text;
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [{ id: 0, text: "zero" }, { id: 1, text: "one" }]
};
}
refresh = () => {
this.setState({ items: [] });
};
render() {
const { items } = this.state;
return (
<div>
{items.map(i => (
<div key={i.id}>
<ListItem item={i} />
</div>
))}
<button onClick={this.refresh}>refresh</button>
</div>
);
}
}
export default List;
You don't need to forceUpdate(), the component will re-render by default when its props are changed.
For an explanation of the fat arrow and what it does to this, check out https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4.

Search results not updating after second search - REACT

I set up a search bar, and after I search the results will pop up. However, the issue is, if I don't refresh the page and search again, it will push me to the new search, but the search results won't update with it. Why would the updated param be showing even though the results aren't updating?
Ex. first url is search/erl,second url is search/Groovy%20Playlist
First search
Second search, query param updated, but search results didn't
Searchbar.js
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = {query: '', results: [], isLoading: false}
}
componentWillMount() {
this.resetComponent()
}
resetComponent = () => this.setState({ isLoading: false, results: [], query: '' })
search(query) {
this.setState({ query });
axios
.get(`/api/search?query=${query}`)
.then(response => {
this.setState({ results: response.data});
})
.catch(error => console.log(error));
}
handleFormSubmit = () => {
console.log('search:', this.state.query);
this.props.action
this.props.history.push(`/search/${this.state.query}`)
this.resetComponent()
}
handleInputChange = (query) => {
this.search(query);
this.setState({ isLoading: true, query })
setTimeout(() =>
this.setState({
isLoading: false,
}) , 300)
}
handleResultSelect = (e, { result }) => this.setState({ query: result.title} )
render () {
const resultRenderer = ({ title }) => <List content = {title}/>
return (
<Form onSubmit={this.handleFormSubmit}>
<Search
loading={this.state.isLoading}
onResultSelect={this.handleResultSelect}
onSearchChange={(event) => {this.handleInputChange(event.target.value)}}
showNoResults={false}
value={this.state.query}
resultRenderer={resultRenderer}
results ={this.state.results}
type={"submit"}
{ ...this.props} />
</Form>
);
}
}
export default withRouter (SearchBar)
Search.js
class Search extends Component {
constructor(props) {
super(props)
this.state = {
results: []
}
}
componentWillMount() {
const { match: { params } } = this.props;
axios
.get(`/api/search?query=${params.query}`)
.then(response => {
console.log(response);
this.setState({ results: response.data });
})
.catch(error => console.log(error));
}
render() {
console.log(this.state.results)
return(
<div>
<div className = "heading centered">
<h1> Search results for: {this.props.match.params.query} </h1>
</div>
{this.state.results.map((post) => {
return(
<Post key = {post.id} post={post}/>
)
})}
</div>
);
}
}
export default Search
Updating results of the SearchBars state will be passed down to Search's props, but you don't work with this.props.results but rather with this.state.results, and that doesnt get updated even if the props change. That works the first time as you reload the Search's state inside componentWillMount but that doesnt get called again as the component is not remounted. Therefore Search always works with its states results, that are never updated.
Now to solve this chaos, remove the componentWillMount logic from Search as that is actually doing what SearchBar already does, and add a listener to componentWillReceiveProps that updates the Searches state, or don't work with the state at all inside Search but take the passed in results instead as this.props.results.
const Search = ({ match, results }) => (
<div>
<div className = "heading centered">
<h1> Search results for: {match.params.query} </h1>
</div>
{results.map((post) =>
<Post key = {post.id} post={post}/>
)}
</div>
);

React Modal Does Not Close In Between Loading Content

I'm using this react modal plugin: https://github.com/reactjs/react-modal
and I need to show an array of objects in the modal on page load. When the first item shows user clicks a button so isOpen prop is set to false for Modal. Each item has a prop showModal that feeds the value to isOpen for the Modal. As the user keeps clicking I keep setting the value on the current object to false and then set it true for the next object.
This is all working fine but the problem is that the overlay and dialog window stays on screen and only content within the modal is updated. I would like the modal to fully close and open to show content of the next object in array. I had to strip out my code to a simplified version below:
class ProductsModal extends React.Component {
constructor(props) {
super(props);
this.remindMeHandler = this.remindMeHandler.bind(this);
this.state = {
products: [],
modalId: 0
};
}
showModals() {
let products = this.state.products;
//A different function saves the array of product objects in the state so
//I can show them one by one
let currentProduct = products[this.state.popUpId];
if (products.length > 0) {
return <ProductItemModal
product={currentProduct}
showNextPopUp={() => this.showNextPopUp(currentProduct.productId)}
showPopUp={currentProduct['showModal']}
/>;
//showModal is a boolean for each product that sets the value of isOpen
}
}
showNextPopUp() {
//All this does is sets the "showModal" property to false for current
//product and sets it to true for next product so it shows in the Modal
}
render() {
return(
<div>
{this.showModals()}
</div>
);
}
}
class ProductItemModal extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<Modal
isOpen={this.props.showModal}
contentLabel="Product"
id={this.props.product.productId}
>
<div>
Product Data......
</div>
</Modal>
);
}
}
Had a workaround for all your problems and created this codepen link. It would be like this,
class ProductItemModal extends React.Component {
render() {
const { showModal, product, showNextModal, onClose } = this.props;
return(
<ReactModal
isOpen={showModal}
contentLabel="Product"
onRequestClose={() => onClose()}
>
<p>
<b>Product Id</b> - {product.id}, <b>Product Name</b> - {product.name}
</p>
<button onClick={() => showNextModal()}>Next</button>
</ReactModal>
);
}
}
class ProductsModal extends React.Component {
constructor() {
super();
this.state = {
products: [
{id: 1, name: "Mac", showModal: true},
{id: 2, name: "iPhone", showModal: false},
{id: 3, name: "iPod", showModal: false},
],
modalId: 0
};
}
handleProductItemModalClose(product) {
//backdrop click or escape click handling here
console.log(`Modal closing from Product - ${product.name}`);
}
showModals() {
const { products, modalId } = this.state;
//A different function saves the array of product objects in the state so
//I can show them one by one
let currentProduct = products[modalId];
if(currentProduct) {
return <ProductItemModal
product={currentProduct}
showNextModal={() => this.showNextModal(currentProduct.id)}
showModal={currentProduct["showModal"]}
onClose={() => this.handleProductItemModalClose(currentProduct)}
/>;
//showModal is a boolean for each product that sets the value of isOpen
}
}
showNextModal(currentProductId) {
const { products, modalId } = this.state;
var isLastModal = false;
if(modalId === products.length - 1) {
isLastModal = true;
}
var clonedProducts = [...products];
var currentIndex = clonedProducts.findIndex(product => product.id === currentProductId);
var newIndex = currentIndex + 1;
clonedProducts[currentIndex].showModal = false;
if(!isLastModal) {
clonedProducts[newIndex].showModal = true;
} else {
//comment the following lines if you don't wanna show modal again from the start
newIndex = 0;
clonedProducts[0].showModal = true;
}
//All this does is sets the "showModal" property to false for current
//product and sets it to true for next product so it shows in the Modal
this.setState({
products: clonedProducts
}, () => {
this.setState({
modalId: newIndex
});
});
}
render() {
return(
<div>
{this.showModals()}
</div>
);
}
}
ReactDOM.render(<ProductsModal />, document.getElementById("main"));
Let me know if it helps.
Updated codepen: https://codepen.io/anon/pen/rzVQrw?editors=0110
You need to call setState() of ProductItemModal to close Model. Otherwise, though isOpen is changed, the UI is not re-rendered.
As you probably know that react maintain a virtual DOM and on every time state or props change it compares the difference between browser's DOM(actual dom) and virtual DOM(the one that React maintain) and in your code every time you change the isOpen property all you are doing is only changing the props of the Model component that's why React only update the internal content of the actual Model
to completely close and re-open the model you need to do a small change in your code
instead of returning only one model component in your ProductsModal you need to do something like this so that react know that this modal has been close and otherone has been open Key property is important for performance reason read more
class ProductsModal extends React.Component {
.
.
.
showModals() {
let products = this.state.products;
//A different function saves the array of product objects in the state so
if (products.length > 0) {
return (
//return list of all modal component
products.map((product) =>
<ProductItemModal
product={product}
showNextPopUp={() => this.showNextPopUp(product.productId)}
showPopUp={product['showModal']}
key={product.productId}
/>
)
);
//showModal is a boolean for each product that sets the value of isOpen
}
}
.
.
.
}
all you are doing here is just returning multiple modal and when one model gets the isOpen props as false is close and the other witch gets true is open and now react know that there are two different modals because of key props
Another work around is to use setTimeout. Implementation is as follows-
class ProductItemModal extends React.Component {
render() {
const { showModal, product, selectNextProductFunc, onClose } = this.props;
return(
<ReactModal
isOpen={showModal}
contentLabel="Product"
onRequestClose={() => onClose()}
>
<p>
<b>Product Id</b> - {product.id}, <b>Product Name</b> - {product.name}
</p>
<button onClick={() => selectNextProductFunc(product)}>Next</button>
</ReactModal>
);
}
}
class ProductsModal extends React.Component {
constructor() {
super();
this.state = {
products: [
{id: 1, name: "Mac"},
{id: 2, name: "iPhone"},
{id: 3, name: "iPod"},
],
productId: null,
showModal: true,
};
}
handleProductItemModalClose(product) {
//backdrop click or escape click handling here
console.log(`Modal closing from Product - ${product.name}`);
}
showModals() {
const { products, productId, showModal} = this.state;
//A different function saves the array of product objects in the state so
//I can show them one by one
const getProduct = function(){
if(productId){
return products.find((i) => i.id === productId);
}else{
return products[0]; // first element
}
}
return <ProductItemModal
product={getProduct()}
selectNextProductFunc={this.selectNextProductFunc.bind(this)}
showModal={showModal}
onClose={() => this.handleProductItemModalClose()}
/>;
//showModal is a boolean for each product that sets the value of isOpen
}
selectNextProductFunc(currentProduct) {
const { products} = this.state;
this.setState({
showModal: false
});
const currentProductIndex = products.findIndex((i) => i.id === currentProduct.id);
const modifiedIndex = 0;
if(products[currentProductIndex + 1]){
this.setState({
productId : products[currentProductIndex + 1].id,
});
}else{
this.setState({
productId : modifiedIndex,
});
}
setTimeout(() => {
this.setState({
showModal: true
})
}, 1000);
}
render() {
return(
<div>
{this.showModals()}
</div>
);
}
}
ReactDOM.render(<ProductsModal />, document.getElementById("main"));
jsbin

Categories

Resources