react material-ui select not working - reactjs

Im' new to react from angularjs, using material-ui for a project and I can't get the select component to work like a select component. Basically I want to populate the dropdown with an array of objects and do something with the selected object once a selection is made by the user. I've been running into a bunch of problems, the most recent is that I can't figure out how to set a default starting value when the component loads and I can't see the selected option in the GUI. I'm able to set the state and log it out to the console you just can't see it in the select component. Also, what is the difference between #material-ui/core and material-ui. Are they different libraries, different versions of the same library?
class HomePage extends React.Component {
constructor(props) {
super();
this.reportSelected = this.reportSelected.bind(this);
this.state = {
report: "report1"
};
}
static propTypes = {
classes: PropTypes.object
};
reports = [
{
name: "report1"
},
{
name: "report2"
},
{
name: "report3"
}
];
reportSelected = event => {
this.setState((prevState) => {
return {
report: event.target.value
}
}, console.log(this.state))
};
render() {
const { classes, headerTitle } = this.props;
return (
<div className={classes.homePage}>
<HeaderTitle title="Home" />
<Helmet>
<title>{headerTitle}</title>
</Helmet>
<form>
<FormControl className={classes.reportsDropdown}>
<InputLabel htmlFor="reports">Reports</InputLabel>
<Select
value={this.state.report}
onChange={this.reportSelected}
>
{this.reports.map(report => (
<MenuItem value={report.name} key={report.name}>
{report.name}
</MenuItem>
))}
</Select>
</FormControl>
</form>
</div>
);
}
}
UPDATE:
The following code works as expected,
class HomePage extends React.Component {
constructor(props) {
super();
this.reportSelected = this.reportSelected.bind(this);
this.state = {
report: "report1"
};
}
static propTypes = {
classes: PropTypes.object
};
reports = [
{
name: "report1"
},
{
name: "report2"
},
{
name: "report3"
}
];
reportSelected = event => {
this.setState(() => {
return {
report: event.target.value
}
})
};
render() {
const { classes, headerTitle } = this.props;
return (
<div className={classes.homePage}>
<HeaderTitle title="Home" />
<Helmet>
<title>{headerTitle}</title>
</Helmet>
<form>
<FormControl className={classes.reportsDropdown}>
<InputLabel htmlFor="reports">Reports</InputLabel>
<Select
value={this.state.report}
onChange={this.reportSelected}
>
{this.reports.map(report => (
<MenuItem value={report.name} key={report.name}>
{report.name}
</MenuItem>
))}
</Select>
</FormControl>
</form>
</div>
);
}
}

I would imagine the problem is that the initial selected value must match a value of an item in the select.
In the code sample you are using the name property this.reports[0].name as the initial value, but your menu items use the object itself for the value, i.e. value={report}.
Either use the name property for the value of the menu items or use this.reports[0] as your initial value and see if that works.
As for your second question, material-ui is the previous version of the library (the 0.xx series). #material-ui is the latest and greatest 1.11 version.

Related

Dynamically creating a new component based off another component's state

First timer here on StackOverflow! I'm trying to emulate a terminal interface for one of my portfolio projects. The way I have envisioned the terminal is that each terminal box has a state object with a few key/value pairs. Ideally, when someone enters text into the terminal box input form, the input becomes disabled and a new terminal box is rendered on the screen with a dynamic response based upon the userInput text which has been saved in the state. Where I'm stuck:
Once userInput state has been updated, how do I get a new terminal box to render beneath the prior box on the screen?
Prior to rendering, how do I set the initial state of the newly-rendered terminal box back to default with the exception of the "output" which would be re-valued to an appropriate response that I set?
How do I access the state in the prior terminal box so I can "read" the userInput stored there so I can determine what the appropriate response to that input would be?
I've included copies of each of the components below:
App.js
import React from "react";
import Terminal from "./components/Terminal";
import "./App.css";
class App extends React.Component {
render() {
return (
<div>
<Terminal />
</div>
);
}
}
export default App;
Terminal.js
import React, { Component } from "react";
import Form from "./Form";
import Falcon from "./Falcon";
import Messages from "./Alerts/Messages";
class Terminal extends Component {
state = {
output: Messages.intro,
userInput: "",
isComplete: false,
isDisabled: "",
};
markComplete = () => {
this.setState({
isComplete: true,
});
};
onSubmit = (event, userInput) => {
event.preventDefault();
this.setState({
userInput: userInput,
isDisabled: "disabled",
});
};
render() {
return (
<div>
<Falcon
output={this.state.output}
markComplete={this.markComplete}
isComplete={this.state.isComplete}
/>
<p />
<Form
input={this.state.userInput}
onSubmit={this.onSubmit}
isComplete={this.state.isComplete}
isDisabled={this.state.isDisabled}
/>
<p />
</div>
);
}
}
export default Terminal;
Falcon.js (Note: You'll see that there is a component "Typed" below - that is part of Matt Boldt's Typed.js (of which react-typed is an offshoot package) package which I'm using to simulate typing.)
import React, { Component } from 'react'
import Typed from 'react-typed'
class Falcon extends Component {
state = {
output: this.props.output,
};
render() {
return (
<div>
<Typed
strings={[this.state.output]}
typeSpeed={40}
onComplete={(self) => {
self.cursor.remove();
this.props.markComplete();
}}
/>
</div>
);
}
}
export default Falcon;
Form.js
import React from "react";
class Form extends React.Component {
state = {
input: this.props.input,
};
render() {
return (
<form
style={{
display: this.props.isComplete === false ? "none" : "",
}}
onSubmit={(event) => {
this.props.onSubmit(event, this.state.input);
}}
>
{"> "}
<input
ref={(input) => input && input.focus()}
type="text"
disabled={this.props.isDisabled}
style={{
border: "none",
outline: "none",
backgroundColor: "#FFF",
color: "#000",
}}
value={this.state.input}
onChange={(event) => this.setState({ input: event.target.value })}
/>
</form>
);
}
}
export default Form;
Any insight or guidance you can offer would be much appreciated! Thank you for helping this "first-timer" out!
Welcome to StackOverflow! I made a codesandbox demo with a few changes.
When developing React applications, it's a good practice to model the UI (the DOM elements) as a function of your internal state. You update the state and the UI changes automatically, it reacts to updates.
That said, you probably want to consider using the form only for the actual input element at the bottom of the terminal. The "past buffer" is just an array that only increases its content with user input and program output. Another good practice (actually a commandment!) is to never mutate the state. So, if you want to update the array, you create a new one from scratch, as in:
this.setState((state) => ({
conversation: [
...state.conversation, // we spread the previous state into the new one
{ text: state.userInput, id: faker.random.uuid(), type: "input" } // the last element is appended
]
}));}
Notice how setState (in class components) just schedules an update for the fields that you used. As your app scales, you will probably want to limit the length of this array.
The terminal component could be like:
class Terminal extends Component {
state = {
output: "Messages.intro",
userInput: "",
isComplete: false,
isDisabled: "",
conversation: [] // couldn't think of a nice name :(
};
markComplete = () => {
this.setState({
isComplete: true
});
};
onChange = (event) => {
this.setState({ userInput: event.target.value });
};
onSubmit = (event) => {
event.preventDefault();
this.setState((state) => ({
userInput: "",
conversation: [
...state.conversation,
{ text: state.userInput, id: faker.random.uuid(), type: "input" }
]
}));
};
render() {
const { conversation, userInput, output, isComplete } = this.state;
return (
<div>
<Falcon
output={output}
markComplete={this.markComplete}
isComplete={isComplete}
/>
<p />
// This is not really a form. Should be changed to another readonly element
{conversation.map((item, index) => (
<Form
key={item.id}
input={item.text}
onSubmit={this.onSubmit}
isComplete={isComplete}
isDisabled
/>
))}
<p />
<Form
input={userInput}
onSubmit={this.onSubmit}
isComplete={isComplete}
isDisabled={false}
onChange={this.onChange}
/>
<p />
</div>
);
}
}

How can I change option styles when get the options dynamically in ChoiceGroup?

I am trying to create choice group with dynamic options. also I need to change the styles of each option. I tried it using classname but it was not success.Can some one help for this?
let numbers = [];
for(let i=0;i<10;i++){
numbers.push({
key:i,text:i,checked: false
});
}
<ChoiceGroup className="numbers" key="numbers" options={numbers} onChanged={this.onRecentMatterChanged}/>
The following example demonstrates how to generate options data for ChoiceGroup component and customize options styling via ChoiceGroup.styles prop:
const generateOptions = (count: number): IChoiceGroupOption[] => {
return Array.from(Array(count), (_, i) => {
return {
key: i.toString(),
text: `Option ${i}`,
checked: false
};
});
};
export default class ChoiceGroupBasicExample extends React.Component<{}, {}> {
private options: any[];
constructor(props: {}) {
super(props);
this.options = generateOptions(10);
}
public render() {
return (
<div>
<ChoiceGroup
className="defaultChoiceGroup"
defaultSelectedKey="4"
options={this.options}
label="Pick one"
required={true}
styles={{
flexContainer: [
{
backgroundColor: "#ADD8E6",
selectors: {
".ms-ChoiceField": {
color: "#00008B",
fontWeight: 600
}
}
}
]
}}
/>
</div>
);
}
}
Details:
flexContainer - the name of container for options
ms-ChoiceField - default class name for option container, via selectors property we override styles
Here is a demo for your reference
Follow Component Styling for a comprehensive guide on how to style components in Fabric

Is My Reactjs Components too Granular?

I am playing around with Reactjs and I am wondering if I broken out my components too much.
<script type="text/babel">
class MainCompoent extends React.Component {
constructor() {
super();
this.state = {
items : [
{
name: 'Test 1',
type: { value: "1"},
types: [
{ value: "2"},
{ value: "1}
]
},
{
name: 'Test 2',
type: { value: "1"},
types: [
{ value: "2"},
{ value: "1}
]
},
]
};
}
componentDidMount() {
}
render() {
return (
<div className="item-container">
{
this.state.items.map((item, i) => {
return <Item item={item} index={i} />
})
}
</div>
)
}
}
class Item extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
render() {
return (
<div className="item">
<General item={this.props.item} index={this.props.index} />
<Specific1 />
<Specific2 />
</div>
)
}
}
class Specific1 extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
render() {
return (
<div className="Specific1-container">
<h1>Specific1 Container </h1>
</div>
)
}
}
class General extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
handleChange(event) {
this.props.handleChange(event.target.value, this.props.index);
}
handleChange2(event) {
this.props.handleChange2(event, this.props.index);
}
render() {
return (
<div className="general-container">
<h1>General Container </h1>
<div>
<label>Name</label>
<input type="text" value={this.props.item.name}/>
</div>
<div>
<label>Type </label>
<select value={this.props.item.type.value} onChange={(event) => this.handleChange(event)}>
{
this.props.item.types.map((type, i) => {
return <option key={'type-' + i} value={type.value} >{type.value}</option>;
})
}
</select>
</div>
</div>
)
}
}
class Specific2 extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
render() {
return (
<div className="specific2-container">
<h1>Specific2 Container </h1>
</div>
)
}
}
ReactDOM.render(<MainCompoent />, document.getElementById("Container"));
</script>
The above code I stripped out everything I don't think was necessary.
I am trying to do this without flux or redux.
In the Item container, there are 3 components that get rendered together. The general will always be shown but only 1 of the other 2 will ever be shown(despite my similar names they are different and have to be 2 separate components). All 3 components do share properties and hence why I put the state in the top and was passing it down.
The one thing though is when I have to update this state I potentially have to go up 2 parents(ie handelChange from General would have to go up to Item which would have to go to MainComponent to update the state).
Is this bad?
I'd stacked with similar question and found to my self a pretty simple answer. It all depends on what level of abstraction do you need.
Eg. if Item component could not "live" without General then the better way is to include General to Item. If they are totally independent then you could simply keep General props-API stable and change itself.
Passing props to the parent is OK, but the better way is to keep one source of truth. If you don't want to use any of data-flow libraries (redux, flux, whteverx) then simply make the Root component as the smartest. Let it to control of app-state changing.
Here is a nice "guide" how to let React components communicate with each other (https://facebook.github.io/react/docs/lifting-state-up.html). Parent to Child via Props, Child to Parent via Callbacks.

React prop only changes state after selecting twice

I am using React Select and for some reason my state is only changing after an option has been selected twice.
I have the following code:
var React = require('react');
import Select from 'react-select';
class VehicleSelect extends React.Component {
constructor(props) {
super(props);
this.state = { brandSelect: ""};
}
_onChange(value) {
//console.log(value) - just to see what we recive from <Select />
this.setState({brandSelect: value});
console.log(this.state.brandSelect);
}
render() {
var options = [
{ value: 'Volkswagen', label: 'Volkswagen' },
{ value: 'SEAT', label: 'SEAT' },
{ value: 'SKODA', label: 'SKODA' }
];
return (
<Select
name="form-field-name"
value={this.state.brandSelect}
options={options}
placeholder="Select a brand"
searchable={false}
onChange={this._onChange.bind(this)}
/>
)
}
};
// Export our component
export default VehicleSelect;
When one of the options is selected it won't console.log the new state however if I select the option again it will. Any idea where I am going wrong, I'm guessing because the state isn't being shown in the console.log it isn't updating?
Thanks
setState does not change state immediately. You need to use a callback. Docs.
_onChange(value) {
//console.log(value) - just to see what we recive from <Select />
this.setState({brandSelect: value}, () => {
console.log(this.state.brandSelect);
});
}

How to call parents method in React without passing it throught props

So i'm building a custom select field. The problem is that the select wrapper and options are different components, so the code for creating such select will be:
<SelectComponent onChange={usersFunction}>
<OptionComponent value="value 1" />
<OptionComponent value="value 2" />
<OptionComponent value="value 3" />
</SelectComponent>
More specifically the problem is that i don't know how to let the SelectComponent know when the option was clicked and witch option was clicked (i don't want to pass any other functions into the code above. I want it only to have onChange function).
What i'm doing right now is in SelectComponent render function i'm wrapping each child in props.children into another div witch has onClick property.
Something like this:
render() {
return {
this.props.children.map(function(item, index){
return (
<div key={index} onClick={this.handleClickMethod.bind(this, item, index)}>{item}</div>
)
})
}
}
Although this is kind of working i'm not really satisfied with the solution. May be there are any other more "react" solutions?
You can use React.cloneElement() to add additional props to children, like so:
render() {
let self=this;
return (
{self.props.children.map(child, index) => {
return React.cloneElement(child, {
onClick: self.handleClickMethod.bind(self, child, index)
})
})}
)
}
Then you can add the handler to the props, without the additional wrapper <div>
UPDATE: Alternatively, you can simply pass the options as an array (instead of as children), like so:
<SelectComponent onChange={usersFunction} options={['value 1','value 2', 'value 3']}>
And build your options component dynamically.
From the official page (on context in react):
We're fond of simply passing the items as an
array
You can do something like,
import React from 'react'
import ReactDOM from 'react-dom'
class OptionComponent extends React.Component{
constructor(props) {
super(props);
}
handleClick(){
this.props.handler(this.props.index);
}
render() {
return (
<div className={this.props.isChecked ? "optionbtn selected" : "optionbtn"} onClick={this.handleClick.bind(this)} data-value={this.props.value}>
<label>{this.props.text}</label>
</div>
);
}
}
class SelectComponent extends React.Component{
constructor() {
super();
this.state = {
selectedIndex: null,
selectedValue: null,
options: ["Option 0","Option 1","Option 2","Option 3"]
};
}
toggleRadioBtn(index){
if(index == this.state.selectedIndex)
return;
this.setState({
selectedIndex: index,
selectedValue: this.state.options[index],
options: this.state.options
});
this.props.onChange.call(this.state.options[index]);
}
render() {
const { options } = this.state;
const allOptions = options.map((option, i) => {
return <OptionComponent key={i} isChecked={(this.state.selectedIndex == i)} text={option} value={option} index={i} handler={this.toggleRadioBtn.bind(this)} />
});
return (
<div data-value={this.state.selectedValue}>{allOptions}</div>
);
}
}
var app = document.getElementById('app');
ReactDOM.render(<SelectComponent onChange={usersFunction}/>, app);
Make method available via context. Require that method in child component and call it.
https://facebook.github.io/react/docs/context.html
Redux is based on this principle.

Resources