In the following component, the handleDropdownChange function does not set the state for "data" on the first change, it only sets it after the second change. For example, when I select 'Electronics', nothing happens. I then select 'Produce', and the page loads all 'Electronics' products.
export default class ProductList extends Component {
constructor(props){
super(props);
this.handleDropdownChange= this.handleDropdownChange.bind(this);
this.state = {
data: fulldata
};
}
handleDropdownChange(e){
e.preventDefault();
var filtered_data = fulldata.filter(prod => prod.category == e.target.value);
this.setState({ data:filtered_data })
}
render(){
return(
<div>Category</div>
<select id="filter-cat" onChange={this.handleDropdownChange}>
<option value="Apparel">Apparel</option>
<option value="Electronics">Electronics</option>
<option value="Produce">Produce</option>
</select>
{this.state.data}
);
}
}
Your code looks okay, although I'm not entirely sure how you're rendering this.state.data because by the looks of it it is an array. You can modify your handleDropdownChange to store the selected item instead.
Your state:
this.state = {
data: fulldata[0]
};
Your onChange handler:
handleDropdownChange(e) {
const filtered_data = fulldata.find(
(prod) => prod.category === e.target.value
);
this.setState({ data: filtered_data });
}
Here's the working codesandbox example
The only thing that worked for me was found here:
setState doesn't update the state immediately
My updated method looks like this:
async handleDropdownChange(e){
e.preventDefault();
var filtered_data = fulldata.filter(prod => prod.category == e.target.value);
await this.setState({ data:filtered_data })
}
This feels hacky and I wish there was a better way, but for now this is only solution that works me.
Related
I'm fairly new to React.
I have selector which returns whatever the user selects.
Code Example:
handleChanged(e){
const { onSelectcountry } = this.props;
onSelectcountry(e.target.value)
}
return (
<div>
<Input type="select" name="select" value={Country} onChange={this.handleChanged.bind(this)}>
{
country.map((item) => {
return (<option value={item._id} key={item._id}> {item.name}</option>);
})
}
</Input>
</div>
);
i dispatch action depend on user select,
import { fetchNews} from '../../actions';
getNews(filterNews) {
const { fetchNews } = this.props;
fetchNews(filterNews);
}
onSelectcountry(country) {
this.setState({ Country: country});
this.getNews({
this.state,
})
}
<CountrySelector onSelectcountry={this.onSelectcountry.bind(this)} Country={Country}/>
The problem is: When the selected value changes, it shows the value of the previous selection.
this is due to the asynchronous nature of setState
You have some options:
use setState's optional callback, it will be invoked after updating state.
onSelectcountry(country) {
this.setState(
{ Country: country},
() => this.getNews({ this.state })
);
}
Call getNews with manually composed arguments
onSelectcountry(country) {
this.setState({ Country: country });
this.getNews({
...this.state,
Country: country
})
}
Call getNews in componentDidUpdate callback, e.g. leave onSelectcountry simple and care only about Country state updates, and handle real state update in expected manner.
componentDidUpdate(prevProps, prevState){
// coundition may vary depending on your requirements
if (this.state.Country !== prevState.Country) {
this.getNews(this.state);
}
}
onSelectcountry(country) {
this.setState({ Country: country});
}
I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}
I am new to react.
For e.g, input value entered 1,2,3,4 and after the event onChange it takes only numbers, then I can
remove 4,3,2 with backspace but not 1. And in HTML DOM also, the 1 cannot be removed.
class House extends Component {
state = {
room: null,
};
componentDidMount() {
if (this.props.house.rent) {
this.setState({ rent: this.props.house.rent });
}
}
onChange = (field, value, mutate) => {
if (field === "houseroom") {
value = parseInt(value.replace(/[#,]/g, ""));
}
mutate({
variables: {
},
});
this.setState({
[field]: value,
});
};
render(){
const {house} = this.props;
<SomeInput
type="text"
value={
(house.room&&
`$${house.room.toLocaleString("en")}`) ||
""
}
onChange={e => {
e.target.placeholder = "Room";
this.onChange("houseroom", e.target.value, mutate);
}}
}
/>
}
It look like a lot of a problem during the update of the state probably due to overrendering elsewhere in the component try to use prevState instead do ensure that state updating can not conflict.
this.setState(prevState => {
[field]:value;
});
Keep me in touch with the result.
Hope this helps someone !
Need to mention "this.state.room" in the input Value and check the prev state using the componentDidUpdate then "this.setState" here and also finally "this.setState" during the event change. Thanks all
I have created dropdown onMouseOver with help of state. So far its working good enough. Because i don't have much knowledge about ReactJS i'm not sure is it possible to make multiple dropdowns with this or different method without writing all code over and over again.
Here is my code:
..........
constructor(props) {
super(props);
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.state = {
isHovering: false
}
}
handleMouseOver = e => {
e.preventDefault();
this.setState({ isHovering: true });
};
handleMouseLeave = e => {
e.preventDefault();
this.setState({ isHovering: false })
};
............
<ul className="menu">
<li onMouseOver={this.handleMouseOver} onMouseLeave={this.handleMouseLeave}>Categories
{this.state.isHovering?(
<ul className="dropdown">
<li>Computerss & Office</li>
<li>Electronics</li>
</ul>
):null}
</li>
</ul>
............
So if I want to add one more dropdown I need to make new state and 2 more lines in constructor() and 2 functions to handle MouseOver/Leave.So repeating amount would be about this:
constructor(props) {
super(props);
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.state = {
isHovering: false
}
}
handleMouseOver = e => {
e.preventDefault();
this.setState({ isHovering: true });
};
handleMouseLeave = e => {
e.preventDefault();
this.setState({ isHovering: false })
};
I will have maybe 10+ dropdowns and at the end will be load of codes. So is there any possibility to not repeat code ? Thank You!
You should use your event.target to achieve what you want. With this, you'll know which dropdown you're hovering and apply any logic you need. You can check for example if the dropdown you're hovering is the category dropdown like this:
if(e.target.className === "class name of your element")
this.setState({hoveredEl: e.target.className})
then you use it this state in your code to show/hide the element you want.
you can check an example on this fiddle I've created: https://jsfiddle.net/n5u2wwjg/153708/
I don't think you're going to need the onMouseLeave event, but if you need you can follow the logic I've applied to onMouseOver
Hope it helps.
1. You need to save the state of each <li> item in an array/object to keep a track of hover states.
constructor(props) {
super(props);
...
this.state = {
hoverStates: {} // or an array
};
}
2. And set the state of each item in the event handlers.
handleMouseOver = e => {
this.setState({
hoverStates: {
[e.target.id]: true
}
});
};
handleMouseLeave = e => {
this.setState({
hoverStates: {
[e.target.id]: false
}
});
};
3. You need to set the id (name doesn't work for <li>) in a list of menu items.
Also make sure to add key so that React doesn't give you a warning.
render() {
const { hoverStates } = this.state;
const menuItems = [0, 1, 2, 3].map(id => (
<li
key={id}
id={id}
onMouseOver={this.handleMouseOver}
onMouseLeave={this.handleMouseLeave}
className={hoverStates[id] ? "hovering" : ""}
>
Categories
{hoverStates[id] ? (
<ul className="dropdown menu">
<li>#{id} Computerss & Office</li>
<li>#{id} Electronics</li>
</ul>
) : null}
</li>
));
return <ul className="menu">{menuItems}</ul>;
}
4. The result would look like this.
You can see the working demo here.
Shameless Plug
I've written about how to keep a track of each item in my blog, Keeping track of on/off states of React components, which explains more in detail.
I'm still learning React, and am following a code-along. I have a form that uses semantic ui react components, after struggling with this problem for a day or so, I just copy and pasted from their GitHub, and am still having the same problem:
onSearchChange = (e, data) => {
clearTimeout(this.timer);
this.setState({
query: data
});
this.timer = setTimeout(this.fetchOptions, 1000);
};
onChange = (e, data) => {
this.setState({ query: data.value });
this.props.onBookSelect(this.state.books[data.value]);
};
render() {
return (
<Form>
<Dropdown
search
fluid
placeholder="Search for a book by title"
value={this.state.query}
onSearchChange={this.onSearchChange}
options={this.state.options}
loading={this.state.loading}
onChange={this.onChange}
/>
</Form>
);
}
}
The console is throwing me a: Warning: Failed prop type: Invalid prop value supplied to Dropdown. It turned out that my value is being passed as an Object that contains my Dropdown component.
As I was writing this question, I realized that I'm in v16 and they were in 15. So, the answer might be as simple as updating the syntax for v16, but I'm not sure how to do that.
Edit:
I installed React 15.x, and that did fix the problem I was having, so I guess my real question now is why doesn't onSearchChange fire fetchOptions when I change data to data.value. fetchOptions is just an axios call that looks like:
fetchOptions = () => {
if (!this.state.query) return;
this.setState({ loading: true });
axios
.get(`/api/books/search?q=${this.state.query}`)
.then(res => res.data.books)
.then(books => {
const options = [];
const booksHash = {};
books.forEach(book => {
booksHash[book.goodreadsId] = book;
options.push({
key: book.goodreadsId,
value: book.goodreadsId,
text: book.title
});
});
this.setState({ loading: false, options, books: booksHash });
});
};
Doing the exact same tutorial (Rem Zolotykh's Build Real Web App with React series) and ran into the same problem with the newer version of React.
Found the issue and how to fix it:
Change the onSearchChange function to use data.searchQuery instead of data or data.value:
onSearchChange = (e, data) => {
clearTimeout(this.timer);
this.setState({
query: data.searchQuery // <-- Right here
});
this.timer = setTimeout(this.fetchOptions, 1000);
};
That makes it work. (Somewhat unexpectedly, leave it as data.value in the onChange function)
Should be an easy fix, all you need to do is update onSearchChange:
onSearchChange = (e, data) => {
clearTimeout(this.timer);
this.setState({
query: data.value
});
this.timer = setTimeout(this.fetchOptions, 1000);
};
You were saving the argument data to this.state.query. You actually need to store data.value to get the string value. Take a look at how onSearchChange works.
onSearchChange(event: SyntheticEvent, data: object)
event: React's original SyntheticEvent.
data: All props, includes current value of searchQuery.