I’m using Material UI v1.0 beta.26 and I’m facing an issue with the dropdown component, in this new version you have to use the Select component combined with MenuItem.
My dropdown is populated when the app is render but when I choose any option from it I’m getting the following error:
And this is my code:
import React from 'react';
import Select from 'material-ui/Select';
import {MenuItem, MenuIcon} from 'material-ui/Menu';
//CONSTANTS
import {CREATE_LS_DISCOUNT_TYPE_DD} from './commons/constants';
import {CREATE_LS_OFFER_TYPE_DD} from './commons/constants';
import cr from '../styles/general.css';
export default class ExampleDropDown extends React.Component {
constructor(props) {
super(props);
this.state = {
DiscountTypeData: [],
OfferTypeData: [],
DiscountTypeState: '',
OfferTypeState: ''
};
this.renderDiscountTypeOptions = this.renderDiscountTypeOptions.bind(this);
this.renderOfferTypeOptions = this.renderOfferTypeOptions.bind(this);
this.handleChangeDiscountType = this.handleChangeDiscountType.bind(this);
this.handleChangeOfferType = this.handleChangeOfferType.bind(this);
}
componentDidMount() {
fetch(CREATE_LS_DISCOUNT_TYPE_DD)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
DiscountTypeData: findResponse.discountTypes,
});
});
}
handleChangeDiscountType(event, index, value) {
this.setState({ DiscountTypeState: (value)});
fetch(CREATE_LS_OFFER_TYPE_DD)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
OfferTypeData: findResponse.offerTypes
});
});
}
handleChangeOfferType(event, index, value) {
this.setState({ OfferTypeState: event.target.value });
}
renderDiscountTypeOptions() {
return this.state.DiscountTypeData.map((dt) => {
return (
<MenuItem
key={dt.id}
value={dt.text}>
{dt.text}
</MenuItem>
);
});
}
renderOfferTypeOptions() {
return this.state.OfferTypeData.map((dt) => {
return (
<MenuItem
key={dt.offerTypeCode}
value={dt.offerTypeDesc}>
{dt.offerTypeDesc}
</MenuItem>
);
});
}
render() {
return (
<div className={cr.container}>
<div>
<Select
value={this.state.DiscountTypeState}
onChange={this.handleChangeDiscountType}>
{this.renderDiscountTypeOptions()}
</Select>
</div>
<br/>
<div>
<Select
value={this.state.OfferTypeState}
onChange={this.handleChangeOfferType}>
{this.renderOfferTypeOptions()}
</Select>
</div>
</div>
);
}
}
in the following method (handleChangeDiscountType) if I leave it like this "this.setState({ DiscountTypeState: value})" I got the error in the screenshot above but if I change that line like this "this.setState({ DiscountTypeState: event.target.value}) it works so I want to understand why
handleChangeDiscountType(event, index, value) {
this.setState({ DiscountTypeState: value});
fetch(CREATE_LS_OFFER_TYPE_DD + 1)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
OfferTypeData: findResponse.offerTypes
});
});
}
also what I want to do is to get the index of my selection in order to pass it to my second web service call but I don't know how to do it, in the previous version of Material UI I just put "index" and works but in the new version ain't work so I want to know a new way to add that parameter.
fetch(CREATE_LS_OFFER_TYPE_DD + PASS INDEX HERE)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
OfferTypeData: findResponse.offerTypes
});
});
I'll appreciate any help with this..
The onChange handler provided to Select is invoked with a target that is enriched with value and name, so you need to pull value from event.target:
handleChangeDiscountType(event) {
const {
DiscountTypeData
} = this.state;
// you're using the text property as the value, but you should probably use its id
// still, here's how you'd find the item using the selected item's value
const selectedDiscount = DiscountTypeData.filter(
discount => discount.text === event.target.value,
);
// use a templated literal to specify the endpoint with the selected item's id
fetch(`${CREATE_LS_OFFER_TYPE_DD}/${selectedDiscount.id}`)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
OfferTypeData: findResponse.offerTypes,
});
});
}
The reason your code was not working is because onChange is not invoked with a third parameter, so your use of value was setting state to undefined.
For more information, see the Selects demo.
Related
I have a react JSX component that makes an api call to the backend and fetches the data I require. This data is currently inside a react-select tag which allows me to display all the options inside a dropdown. I'm trying now to use this dropdown to render the selection using charts, specifically react-chartjs-2. I've been trying for quite a while but I honestly am at my wits end.
Here's what the api call with the select looks like:
export default class ------ extends component{
constructor(props, context) {
super(props, context);
this.state = {
selectedOption: {},
};
}
fetchData = (inputValue, callback) => {
setTimeout(() => {
fetch(
"api" +
inputValue,
{
method: "GET",
}
)
.then((resp) => {
return resp.json();
})
.then((data) => {
const tempArray = [];
if (data) {
if (data.length) {
data.forEach((element) => {
tempArray.push({
label: `${element.campo}`,
value: element.valor,
});
});
} else {
tempArray.push({
label: `${data.campo}`,
value: data.valor,
});
}
}
callback(tempArray);
})
.catch((error) => {
console.log(error, "error");
});
}, 1000);
};
onSearchChange = (selectedOption) => {
if (selectedOption) {
this.setState({
selectedOption,
});
}
};
Here's the actual select element and the chart inside the return section
render(){
return(
<AsyncSelect
id="valx"
value={this.state.selectedOption}
loadOptions={this.fetchData}
placeholder="placehold"
onChange={(e) => {
this.onSearchChange(e);
}}
defaultOptions={true}
/>
<div class="charts">
<Bar
id="MyBarChart"
data={{}}
options={{}}
/>
<Pie
id="MyPieChart"
data={{}}
options={{}}
/>
</div>
I have a really long way to go when it comes to React, I'm sure the solution will turn out to be rather simple. Any and all help is welcome, and thanks in advance.
Components
const Pcards = ({ projects }) => {
return (
<div>
<CardColumns>
{projects.map((projects) => (
<Card>
<Card.Img variant="top" src={"http://localhost:8000" + projects.images[0].file_path + projects.images[0].file_name + projects.images[0].file_type} />
Pages
class Projects extends Component {
state = {
projects:[]
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
this.setState({ projects: data })
})
.catch(console.log)
}
render () {
return (
<Pcards projects = {this.state.projects} />
);
}
}
New to react and this code returns
TypeError: projects.map is not a function
This appears to be compiling just fine on my partner's end since he written this code and I'm trying to expand on his work.
I've seen other similar posts but unable to find a fix. Any idea what's going on here?
You have two mistakes in your Projects class.
1- .catch error handling syntax was wrong
2- you were not checking the fetched data
class Projects extends Component {
state = {
projects: []
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
if (data && data.length) { // checking the data
this.setState({ projects: data })
} else {
console.log("Projects fetch failed, check your api code!")
}
})
.catch(e => console.log(e)); // corrected error catch
}
render() {
return (
<Pcards projects={this.state.projects} />
);
}
}
You can also edit your Pcards component code. You are already using a property called projects and you are mapping it, calling the argument projects too. That is not a good practice. If you are mapping projects name the item as project or projectItem.
projects.map((project) => ...
Try
class Projects extends Component {
constructor(props){
super(props)
this.state = {
projects:[]
}
}
I am working on a hacker news clone I am trying to get the ids of the top stories from their api using axios in componentDidMount and then making another axios call to get the stories and push them in a state array but when I try to map over and render that array nothing shows up
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.state.posts.push(value)
})
.catch(err =>{
console.log(err)
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
<Header title="Hacker News" />
{this.state.posts.map( (element, index) => <Post key={element.data.id} serialNum={index} postTitle={element.data.title} postVotes={element.data.score} postAuthor={element.data.by} />) }
</div>
)
}
}
Try setting the state like this:
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({
posts: [value, ...this.state.posts]
})
})
.catch(err =>{
console.log(err)
})
})
This way you're using setState and appending every new value to the existing state.
As stated in the comments, don't use push for set state. In your code when you make the second request you must change the setState method to spread out the new value.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState(prevState => ({posts: [ value.data, ...prevState.posts]}))
})
.catch(err =>{
console.log("err");
console.log(err);
})
})
})
.catch(err => {
console.log(err);
})
}
render() {
return (
<div>
{this.state.posts && this.state.posts.map( (element, index) =>
<div key={element.id}>
{element.title}
</div>
)}
</div>
);
}
}
componentDidMount() is called after Render() only once. React doesn't know about the state changes unless you use setState().
componentDidMount() {
axios.get('https://hacker-news.firebaseio.com/v0/topstories.json')
.then( result => {
result.data.slice(0, 10).forEach(element => {
axios.get('https://hacker-news.firebaseio.com/v0/item/' + element + '.json')
.then( value => {
this.setState({posts: [value, ...this.state.posts]})
})
})
})
}
Use this.setState({posts : [value, ...this.state.posts]}) instead of this.state.posts.push(value). using ... (spread operator) appends the value to the original posts array.
I have two dropdowns, Discount Type and Offer Type, Discount type returns four elements so what I want to do is for example if I select option number 2 from that dropdown then call the URL that populates the Offer Type dropdown with the selected index, in this case '2', because now the offer type is returning all because I'm using the following URL that brings all: http://xxxxxx:8080/services/OfferType/getAll but instead of getAll I want to pass the index of the Offer Type Dropdown to have something like this http://xxxxxx:8080/services/OfferType/2
Any help on how to do this because I don't, below you'll find my current code:
import React from 'react';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
import Divider from 'material-ui/Divider';
import cr from '../styles/general.css';
export default class ExampleDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
DiscountTypeData: [],
OfferTypeData: [],
DiscountTypeState: '',
OfferTypeState: ''
};
this.handleChange = this.handleChange.bind(this);
this.renderDiscountTypeOptions = this.renderDiscountTypeOptions.bind(this);
this.renderOfferTypeOptions = this.renderOfferTypeOptions.bind(this);
this.handleChangeDiscountType = this.handleChangeDiscountType.bind(this);
this.handleChangeOfferType = this.handleChangeOfferType.bind(this);
}
componentDidMount() {
const offerTypeWS = 'http://xxxxxx:8080/services/OfferType/getAll';
const discountTypeWS = 'http://xxxxxx:8080/services/DiscountType/getAll';
fetch(offerTypeWS)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
OfferTypeData: findResponse.offerTypes
});
});
fetch(discountTypeWS)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
DiscountTypeData: findResponse.discountTypes
});
});
}
handleChange(event, index, value) {
this.setState({value});
}
handleChangeDiscountType(event, index, value) {
this.setState({ DiscountTypeState: (value) });
}
handleChangeOfferType(event, index, value) {
this.setState({ OfferTypeState: (value) });
}
renderDiscountTypeOptions() {
return this.state.DiscountTypeData.map((dt, i) => {
return (
<MenuItem
key={i}
value={dt.text}
primaryText={dt.text} />
);
});
}
renderOfferTypeOptions() {
return this.state.OfferTypeData.map((dt, i) => {
return (
<MenuItem
key={i}
value={dt.offerTypeDesc}
primaryText={dt.offerTypeDesc} />
);
});
}
render() {
return (
<div className={cr.container}>
<div className ={cr.boton}>
<Divider/>
<br/>
</div>
<div>
<DropDownMenu
value={this.state.DiscountTypeState}
onChange={this.handleChangeDiscountType}>
<MenuItem value={''} primaryText={'Select discount type'} />
{this.renderDiscountTypeOptions()}
</DropDownMenu>
<br/>
<DropDownMenu
value={this.state.OfferTypeState}
onChange={this.handleChangeOfferType}>
<MenuItem value={''} primaryText={'Select offer type'} />
{this.renderOfferTypeOptions()}
</DropDownMenu>
</div>
</div>
);
}
}
This is the response from the Discount Type service:
So if I select "Bulk Discount" that has the value "3" then I want to pass that 3 to the Offer Type URL..
You can call fetch from handleChangeDiscountType or handleChangeOfferType just like you called in componentDidMount. Example:
handleChangeDiscountType(event, index, value) {
fetch('http://xxxxxx:8080/services/DiscountType/' + value.id)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({ DiscountTypeState: findResponse });
});
}
I have this piece of code that calls a web service and displays the names coming from that WS into a Dropdown component from Material UI,
What I want to do is to set the default value of the dropdown with the first element coming from the WS and also be able to select any of the options in dropdown, I read something about "State" but don't get it really good at a code level.
I'm new to React and learning by myself but some help would be nice.
import React, { Component } from 'react';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
export default class WebserviceTest extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
this.renderOptions = this.renderOptions.bind(this);
}
componentDidMount() {
const url = 'https://randomuser.me/api/?results=4';
fetch(url)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
data: findResponse.results
});
});
}
//will set wahtever item the user selects in the dropdown
handleChange(event, index, value) {this.setState({ value });}
//we are creating the options to be displayed
renderOptions() {
return this.state.data.map((dt, i) => {
return (
<div key={i}>
<MenuItem
label="Select a description"
value={dt.name.first}
primaryText={dt.name.first} />
</div>
);
});
}
render() {
return (
<div>
<DropDownMenu value={this.state.name} onChange={this.handleChange}>
{this.renderOptions()}
</DropDownMenu>
</div>
);
}
}
Thanks in advance.
Regards.
You need to set state of selected dropdown option. And set first value of data as selected value.
componentDidMount() {
const url = 'https://randomuser.me/api/?results=4';
fetch(url)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
data: findResponse.results,
selected: findResponse.results[0].name.first // need to be sure it's exist
});
});
}
handleChange(event, index, value) {this.setState({ selected: value });}
.
.
.
render() {
return (
<div>
<DropDownMenu value={this.state.selected} onChange={this.handleChange}>
{this.renderOptions()}
</DropDownMenu>
</div>
);
}
UPDATED CODE
import React, { Component } from 'react';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
export default class WebserviceTest extends Component {
constructor() {
super();
this.state = {
data: [],
selected: '',
};
this.renderOptions = this.renderOptions.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
const url = 'https://randomuser.me/api/?results=4';
fetch(url)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
console.log(findResponse.results[0].name.first);
this.setState({
data: findResponse.results,
selected: findResponse.results[0].name.first // need to be sure it's exist
});
});
}
handleChange(value) {this.setState({ selected: value });}
//we are creating the options to be displayed
renderOptions() {
return this.state.data.map((dt, i) => {
return (
<div key={i}>
<MenuItem
value={dt.name.first}
primaryText={dt.name.first} />
</div>
);
});
}
render() {
return (
<div>
<DropDownMenu value={this.state.selected} onChange={this.handleChange}>
{this.renderOptions()}
</DropDownMenu>
</div>
);
}
}
this.setState controls the variable this.state in a special way. Whenever you use this.setState it will run render again to check for changes accordingly. Your dynamic content that you want to be responsive should be placed in this.state and those should be shown in your render function.
There are many ways to go about solving your question, but the most important principle to use is to place what you currently want to render (or the id/index number) in this.state and use this.setState to change it as needed.
value={this.state.name} should be a single value from your data structure that you return from your fetch, assuming this is what is shown on the screen.
Also, you forgot to bind this.handleChange in your constructor.
Stating props in your constructor is perfectly fine to do. You only do that when you want to use something from this.props in your constructor. You aren't, so it's perfectly safe to leave it as constructor() and super()