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
Related
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>
);
};
I have been doing some research on how to implement dark mode without using hooks. Unfortunately, I could not find any examples. However, I tried to implement it with the following code:
import React, { Component } from "react";
import { CountryList } from "./Components/Card-List/CountryList";
import { SearchBox } from "./Components/Search-box/Search-Box";
import "./Countries.styles.css";
class Countries extends Component {
constructor() {
super();
this.state = {
countries: [],
searchField: "",
regionField: "",
darkMode: false,
};
this.setDarkMode = this.setDarkMode.bind(this);
}
componentDidMount() {
fetch("https://restcountries.eu/rest/v2/all")
.then((response) => response.json())
.then((all) => this.setState({ countries: all, regions: all }));
}
setDarkMode(e) {
this.setState.darkMode((prevMode) => !prevMode);
}
render() {
const { countries, searchField, regionField, darkMode } = this.state;
const filterCountries = countries.filter(
(country) =>
country.name.toLowerCase().includes(searchField.toLowerCase()) &&
country.region.toLowerCase().includes(regionField.toLowerCase())
);
return (
<div className={darkMode ? "dark-mode" : "light-mode"}>
<nav>
<h1 className="header">Where in the World</h1>
<button onClick={this.setDarkMode}>Toggle Mode</button>
<h1>{darkMode ? "Dark Mode" : "Light Mode"}</h1>
</nav>
<div className="Input">
<SearchBox
type="search"
placeholder="Search a Country"
handlechange={(e) =>
this.setState({
searchField: e.target.value,
})
}
/>
<SearchBox
type="regions"
placeholder="Filter by Regions"
handlechange={(e) =>
this.setState({
regionField: e.target.value,
})
}
/>
</div>
<CountryList countries={filterCountries} />
</div>
);
}
}
export default Countries;
The error I am getting is this.setState.darkMode is not a function. Not sure what I am missing. Any help would be appreciated. Implementing this with hooks seems quite straight forward.
this.setState is a function, I think you trying to use setState with callback like so:
this.setState((prevState) => ({ darkMode: !prevState.darkMode }));
this.setState is a function to update the state property of your Class Component in react. So you cannot access darkMode property using this.setState.darkMode. If you want to read darkMode property then you have to use:
let isDarkMode = this.state.darkMode
if you want to set darkMode property then you have to use:
this.setState({darkMode: true})
Now if you want reverse your darkMode property then you should use:
this.setState((prevState) => { darkMode: !prevState.darkMode });
I have already a a click event within a ternary operator that does a GET request from my API. When the button is clicked, the button disappears and the data text replaces the button (button disappears). But there is a small gap of time between the get request and the text reveal. I want to put a loading message of some kind at that moment of time so the user knows something is happening. But can't seem to figure it out. Here is my code:
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
})
console.log(meal)
})
};
combinedFunction() {
this.fetchData();
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<div >
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
</div>
);
}
}
Appreciate the help.
What you can do is add a "isLoading" state and put the values before and after your API call like so:
fetchData() {
this.setState({isLoading: true});
axios.get('api/menu/food')
.then(res => {
const meal= `${res.data.starters},${ res.data.price}`;
this.setState({
meal: meal,
isLoaded: true
isLoading: false,
})
console.log(meal)
})
};
And use that on your render to show the "loading icon"
render(){
const {isLoaded, meal, isLoading } = this.state;
return (
<div >
{isLoading ? <div>loading</div> :
Dish: {
this.state.clicked ? (
this.state.menu
) : (
<button onClick={() => { this.combinedFunction() }}>Find Dish</button>
)}
}
</div>
);
}
}
This is a working demo which shows loading when api call starts and disables button to prevent multiple api calls. I added a 2sec time out to show the demo. Check the stackblitz sample
This is the updated code, here I used a fake api (https://jsonplaceholder.typicode.com/users) to show the demo
import React, {Component} from "react";
import axios from "axios";
export default class FoodData extends React.Component {
constructor(props) {
super(props);
this.state = {
meal: '',
clicked: false,
isLoaded: false,
}
this.handleClick = this.handleClick.bind(this);
this.combinedFunction = this.combinedFunction.bind(this)
}
handleClick() {
this.setState({
clicked: true,
});
}
fetchData() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then(res => {
this.setState({
meal: res.data,
isLoaded: false
})
})
};
combinedFunction =()=> {
this.setState({isLoaded: true})
setTimeout(()=>{
this.fetchData();
},2000)
this.handleClick();
}
render(){
const {isLoaded, meal} = this.state;
return (
<>
<div >
Users:
<button onClick={this.combinedFunction } disabled={isLoaded ? true : false}>{isLoaded ? 'Loading...':'Find User'}</button>
</div>
<div>
{meal && meal.map(item =>(
<div key={item.id}>
<p>{item.id} - {item.name}</p>
</div>
))}
</div>
</>
);
}
}
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>
);
I'm making a movie search page. When I search something, it goes through the data base and find the very first match and display on the page. However, I want to create a function, so when I click next, page displays next movie in the data base. My code follows:
import React, { Component, PropTypes } from 'react';
import SearchBar from './Bar/index.js';
import SearchResult from './Result/index.js';
import axios from 'axios';
import './index.css';
class SearchArea extends Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
searchResult: {},
result: false,
count: 0
};
}
handleSearchBarChange(event) {
this.setState({searchText: event.target.value});
}
handleSearchBarSubmit(event) {
event.preventDefault();
const movie = this.state.searchText;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=c6cd73ec4677bc1d7b6560505cf4f453&language=en-US&query=${movie}&page=1&include_adult=false`)
.then(response => {
if(response.data.results.length >= 0) {
const i = 0;
const {
title,
overview,
release_date: releaseDate
} = response.data.results[this.state.count];
const posterPath = 'https://image.tmdb.org/t/p/w154' + response.data.results[this.state.count].poster_path;
this.setState({
searchResult: {
title,
posterPath,
overview,
releaseDate
},
result: true
});
}
else {
this.setState({
searchResult: {
title: 'No Result',
overview: 'No Overview Available',
posterPath: ''
},
result: true
});
}
})
}
handleSearchNext(event) {
this.handelSearchBarSubmit.overview = response.data.results[1];
}
handleResultClose() {
this.setState({
searchResult: {},
result: false
});
}
render() {
return (
<div>
<SearchBar
value = {this.state.searchText}
onChange = {this.handleSearchBarChange.bind(this)}
onSubmit = {this.handleSearchBarSubmit.bind(this)}
onNext = {this.handleSearchNext.bind(this)}
/>
{this.state.result &&
<SearchResult
searchResult = {this.state.searchResult}
onClose = {this.handleResultClose.bind(this)}
onAdd = {this.props.onAdd}
/>
}
</div>
);
}
}
SearchArea.propTypes = {
onAdd: PropTypes.func.isRequired
};
export default SearchArea;
I can't seem to figure out how to make handleSearchNext. Please help
EDIT
Following is the SearchBar code
import React, { PropTypes } from 'react';
import { Button } from 'semantic-ui-react';
import styles from './index.css';
const SearchBar = (props) => {
return (
<div>
<form onSubmit={(event) => props.onSubmit(event)}>
<input
className="searchBar"
type="text"
placeholder="Search Here"
value={props.value}this
onChange={(event) => props.onChange(event)}
onNext={(event) => props.onChange(event)}
onBack={(event) => props.onChange(event)}
/>
<Button className="button" type="submit">Sumbit</Button>
</form>
<Button className={styles.button} type="previous">Back</Button>
<Button className="button" type="next">Next</Button>
</div>
);
};
SearchBar.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
onNext: PropTypes.func.isRequired
};
export default SearchBar;
You could have your server respond with not only the requested title, but also the next one. That way, when you click on Next, you can immediately display the next movie without waiting for a response, while still querying it in the background by name or id (so that you have the next after it, etc.).
Edit: If I misunderstood what you meant and you already have this (it looks like you are actually querying a whole page of movies at once), you probably simply want something like
handleSearchNext(event) {
this.setState({ searchResult: response.data.results[1], result: true });
}
and handle specially the case when you hit the last item on the page.