I am trying to default check the first radio button which the following code helps me to do. When loaded the page the first radio button is checked but the problem i am facing is that it doesn't allow me to check the other buttons that also are present in the array.
constructor(props: any) {
super(props);
this.state = {
selectedSort: '',
sort: ['Apple', 'Orange '],
}
}
this.state.sort.map((sortType:string, index:number) => {
return <span key={`${sortType}${index}` onClick={() => this.setSort(sortType)} >
<input type="radio" id={sortType}
value={this.state.selectedSort}
name={sortType} defaultChecked={index===0}
}/>
<span>{sortType}</span>
})
private setSort = (selectedSort: string) => {
this.setState({
selectedSort: selectedSort
});
}
Issue
The defaultChecked value is a boolean but your condition sortType === 0 will always evaluate false since your sortType is only ever one of your sort state values, i.e. ["Apple", "Orange "].
Solution
If you want the first radio button to be default checked then you should compare against the mapped index.
defaultChecked={index === 0}
Other Issues & Suggestions
Radio button group inputs should all have the same name attribute.
Use a semantic label to wrap your inputs so they are more accessible.
Use the radio input's onChange event callback versus an onClick, to update state.
The sortType values alone should be sufficient for a React key.
Code:
{this.state.sort.map((sortType, index) => (
<label key={sortType}>
<input
type="radio"
id={sortType}
value={selectedSort}
name="sortType"
defaultChecked={index === 0}
onChange={(e) => this.setState({ selectedSort: e.target.id })}
/>
{sortType}
</label>
))}
Additionally, I suggest converting this to a fully controlled input since you have already all the parts for it. Remove the value attribute and use the checked prop. Set what you want the initial checked state to be. This will allow you have have already valid checked state.
state = {
selectedSort: 'Apple',
sort: ['Apple', 'Orange '],
}
{this.state.sort.map((sortType, index) => (
<label key={sortType}>
<input
type="radio"
id={sortType}
name="sortType"
checked={sortType === this.state.selectedSort}
onChange={(e) => this.setState({ selectedSort: e.target.id })}
/>
{sortType}
</label>
))}
Demo
Related
I am trying to build a small recipe app. One feature of the app is saving user recipes, including ingredient/qty/measurement.
I need to wrap up the inputted ingredients into an array of objects to send to server but right now my setIngredientList only works for the first two ingredients a user inputs.
When a user tries to add a third ingredient it just mirrors the data from the second input (and fills the third input's fields with the same data as the second input). It is like the second inputs and any subsequent input mirror each other.
I believe the problem is init is not clearing properly (it seems to clear after the first ingredient is added allowing the second one to be added, but then it does not clear for the next ingredients.
I'm not sure the proper way to make sure this happens so multiple ingredients can be added.
Here is my code:
const init = {
ingredient_name: '',
quantity: '',
measure: '',
}
export default function Recipe() {
const [name, setName] = useState('')
const [ingredientList, setIngredientList] = useState([
{
ingredient_name: '',
quantity: '',
measure: '',
},
])
const handleChange = (e, i) => {
const { name, value } = e.target
setIngredientList((prevState) => {
const newIngredientList = [...prevState]
newIngredientList[i][name] = value
return [...newIngredientList]
})
}
return (
<div>
<div className="recipe-form-container">
<form className="recipe-form">
[...]
</div>
{ingredientList.map((list, i) => (
<div key={i} className="ingredient-triad">
<input
className="ingredient"
name="ingredient_name"
type="text"
value={list.ingredient_name}
onChange={(e) => handleChange(e, i)}
></input>
<input
className="quantity"
name="quantity"
type="text"
value={list.quantity}
onChange={(e) => handleChange(e, i)}
></input>
<select
className="dropdown"
name="measure"
id="measure"
value={list.measure}
onChange={(e) => handleChange(e, i)}
>
<option value="" disabled>
--none--
</option>
<option value="cup">cup</option>
</select>
<button
onClick={(e) => {
console.log(init)
setIngredientList((prev) => [...prev, init])
e.preventDefault()
}}
>
Add
</button>
</div>
))}
</form>
</div>
</div>
)
}
Classical object reference issue.
Use the below code it will work fine.
Previously, you pass the same init object for multiple rows,
which is why you got that result. Instead of doing that, when the user clicks 'add' button then add a new Object to your state which is derived from your init object. Here I just clone the init object and then set the state.
<button
onClick={(e) => {
console.log(init);
setIngredientList((prev) => [...prev, { ...init }]);
e.preventDefault();
}}
>
Sounds like you wanted some advice and can prob find the solution yourself, but what i would do to this code:
Move e.preventDefault() above setIngredientList.
Create an Ingredient class with the state / logic within and pass state to Ingredient, the list should not not be concerned with an Ingredient, it just lists.
Then what is onClick doing - init - its the initial value isn't it, so I think that with your onClick you are overwriting prev with the value that is outside the component - should it be outside or is it some state - it holds a value - so it should be an initial state of an ingredient? Checkout react tools profiler so see exactly what is happening on your events.
So what is prev we are over writing it, but what is it? Isn't the first arg on onClick the click event? So I think you are overwriting the click event with the init state - which is outside the component - a few things to tweak for you (if you like), hope it helps.
I'm building a component dynamically with many inputs, and I need to handle the onChange on these inputs...
So what I tired is to set a name for each input control and then on onChange call to set a state variable with the name of that control, and later on to use it from state to fill the "value" property of the input control :
else if (element.Type === TEXT) {
elements.push(
<td className="view-cell" align="left">
{element.Name}<br/>
<Input key={element.Id}
name={element.Name.toLowerCase().replace(" ","")}
// label={element.Name}
// defaultValue={element.Value}
value={this.state.valueOf(element.Name.toLowerCase().replace(" ",""))}
disabled={!element.Editable}
onChange={(e) => {
this.onInputValueChange(e)
}}
/>
</td>
)
}
onInputValueChange(e) {
this.setState({[e.target.name]: e.target.value})
console.log(this.state.valueOf(e.target.name))
}
But I can see that the state is not updated with the new values that I'm trying to add to it dynamically by the inputs names!
How can I achieve it?
Update
When I try to access the state value from console I'm getting:
I noticed two things on your code:
You are trying to get the values from valueOf state variable. But you're missing to set the values in valueOf object in your onchange handler. If you add the missing bits, it should be fine.
onInputValueChange(e) {
this.setState({
[e.target.name]: e.target.value
}, () => {
console.log(this.state[e.target.name]);
// use [] to access value instead of ()
});
}
Also, you have to access the values dynamically from an object by using square bracket [] notation.
if (element.Type === TEXT) {
const inputName = element.Name.toLowerCase().replace(" ","");
elements.push(
<td className="view-cell" align="left">
{element.Name}<br/>
<Input
key={element.Id}
name={inputName}
label={element.Name}
defaultValue={element.Value}
value={this.state[inputName]}
disabled={!element.Editable}
onChange={this.onInputValueChange}
/>
)
}
This should fix your issue.
I am trying to display a textbox based on some selected value in dropdown in react.
my code is
<label>
color:
<select value="Radish" onChange ={this.checkColor}>
<option value="Orange">Orange</option>
<option value="Radish">Radish</option>
<option value="Cherry">Cherry</option>
</select>
<input type="text" name="project Id" id="project Id" style={{display:'none'}}/>
<label>
Onchange, the below function will be invoked.
CheckLayers(evt){
if(evt.target.value === 'Orange'){
this.setState({showField:true})
}
}
But I am not able to see the textbox. I tried to put condition based on showField but that's not working.
(this.state.showField)
<input type="text" name="project Id" id="project Id" style={{display:'none'}}/>
Please advice!!
There are a few things to check in your code.
First, with your current implementation of the <select> and <option> elements, the actual value on the <select> will never change because it is always set to "Orange". You need to also save the last selected <select> value in your component's state:
checkLayers(event) {
const { value } = event.target;
this.setState({
showField: value === "Orange",
selectedValue: value
});
};
And in your render method use the selectedValue accordingly:
render() {
return (
<label>
color
<select value={this.state.selectedValue} onChange={this.checkLayers}>
/* {your options} */
</select>
</label>
);
}
The next thing to check is the checkLayers method. Inside this method you are trying to access this. Since the <select>'s onChange callback is going to be called on a different context as your component (you can check this by logging this inside checkLayers). As a result of this, you need to bind the checkLayers method to your component's context. This can be done by explicitly binding the function when passing it as a callback or by defining the class method as an arrow function.
Binding option (in the render method):
<select value={selectedValue} onChange={this.checkLayers.bind(this)}>
Class method as Arrow function option:
class MyComponent extends React.Component {
...
checkLayers = (event) => {
const { value } = event.target;
this.setState({
showField: value === "Orange",
selectedValue: value
});
};
...
There is also an alternative option using arrow functions:
<select value={selectedValue} onChange={(event) => this.checkLayers(event)}>
You can learn more about thishere
The last thing to check is how you are trying to conditionally render the <input> element. Right now you are hardcoding the style as { display: 'none' }. You could change this based on the value of showField, but I think is it better only render the <input> when showField is true, like this:
render() {
const { selectedValue, showField } = this.state;
return (
<label>
color:
<select value={selectedValue} onChange={this.checkLayers.bind(this)}>
/* {your options} */
</select>
{showField ? (
<input
type="text"
name="project Id"
id="project Id"
/>
) : null}
</label>
);
}
Here we are using the ternary operator to decide if we render the element or we render nothing (using null).
You can see the final implementation here:
Comment if you have further questions, cheers!
I cannot find documentation on how to invoke onChange for radio buttons using react-bootstrap. I left out the button which submits the form. It works with every other field, like text inputs, so I left that out.
For each, teacherRate and overallRate, each radio button has a value of 1, 2, 3 but I am not sure how to tie that in.
I also know that I cannot have the values be the same for each category.
I am not looking to do Button Groups.
I looked online for similar answers but cannot find any. There was one person who posted their problem like mine but answered later saying he implemented react-bootstrap but did not post his solution. I cannot reply as I do not have enough points.
class Assignment extends Component {
constructor(props){
super(props)
this.state = {
form: {
teacherRate: '',
overallRate: ''
}
}
}
handleChange(event){
const formState = Object.assign({}, this.state.form)
formState[event.target.name] = event.target.value
this.setState({form: formState})
}
render() {
return (
<Grid>
<form>
<FormGroup>
<ControlLabel id='adminRate'>Rate the Teacher</ControlLabel>
<Radio name='adminRate' type='integer' inline
onChange={this.handleChange.bind(this)}
value={this.state.form.adminRate}>1</Radio>{' '}
<Radio name='adminRate' type='integer' inline
onChange={this.handleChange.bind(this)}
value={this.state.form.adminRate}>2</Radio>{' '}
<Radio name='adminRate' type='integer' inline
onChange={this.handleChange.bind(this)}
value={this.state.form.adminRate}>3</Radio>{' '}
</FormGroup>
<FormGroup>
<ControlLabel id='adminRate'>Overall Rating</ControlLabel>
<Radio name='adminRate' type='integer' inline
onChange={this.handleChange.bind(this)}
value={this.state.form.adminRate}>1</Radio>{' '}
<Radio name='adminRate' type='integer' inline
onChange={this.handleChange.bind(this)}
value={this.state.form.adminRate}>2</Radio>{' '}
<Radio name='adminRate' type='integer' inline
onChange={this.handleChange.bind(this)}
value={this.state.form.adminRate}>3</Radio>{' '}
</FormGroup>
</form>
</Grid>
);
}
}
The onChange event is being fired, but your handleChange function is not doing what you are expecting.
handleChange(event){
const formState = Object.assign({}, this.state.form)
formState[event.target.name] = event.target.value
this.setState({form: formState})
}
If you take a look in a debugger, event.target does not have a name or value attribute for a check box. It simply has a checked attribute. You will have to find another way to get the information you are looking for out of the target.
To piggyback off the previous poster, in your Radio buttons you have value={this.state.form.adminRate} but adminRate is never defined in
this.state = {
form: {
teacherRate: '',
overallRate: ''
}
So when you return formState[event.target.name] = event.target.value from your handleChange function its not inputting any value into the event.target.name. So you may just want to input 1, 2, and 3 as the value in the corresponding buttons.
Also even if it did input values, event.target.name in your Radio buttons are adminRate so you'd run into the this.state problem again, so you'd have to convert name={adminRate} to name={teacherRate} in the first FormGroup buttons so that the formState object in
handleChange(event){
const formState = Object.assign({}, this.state.form)
formState[event.target.name] = event.target.value
this.setState({form: formState})
}
points to the teacherRate property inside the form object defined in this.state when you call this.setState({form: formState}) at the end of the function.
I don't know the relevance of using the 'Overall Rating' radio buttons so I can't assist with the second FormGroup but it would basically be the same logic as the first. You'd need Math logic to get the average number for the overall rating though.
You need to set the checked attribute on the <Radio> element as a response to some state/props, the following is a snippet from a Formik / react-bootstrap form:
<Radio
name="upload_radio"
checked={status.upload_radio === 'textarea'}
value="textarea"
onChange={e => setStatus({ upload_radio: e.target.value })}
>
I'm trying to create a simple form with react, but facing difficulty having the data properly bind to the defaultValue of the form.
The behavior I'm looking for is this:
When I open my page, the Text input field should be filled in with the text of my AwayMessage in my database. That is "Sample Text"
Ideally I want to have a placeholder in the Text input field if the AwayMessage in my database has no text.
However, right now, I'm finding that the Text input field is blank every time I refresh the page. (Though what I type into the input does save properly and persist.) I think this is because the input text field's html loads when the AwayMessage is an empty object, but doesn't refresh when the awayMessage loads. Also, I'm unable to specify a default value for the field.
I removed some of the code for clarity (i.e. onToggleChange)
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message
{awayMessage: {}}
onTextChange: (event) ->
console.log "VALUE", event.target.value
onSubmit: (e) ->
window.a = #
e.preventDefault()
awayMessage = {}
awayMessage["master_toggle"]=#refs["master_toggle"].getDOMNode().checked
console.log "value of text", #refs["text"].getDOMNode().value
awayMessage["text"]=#refs["text"].getDOMNode().value
#awayMessage(awayMessage)
awayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
console.log "AWAY_MESSAGE", this.state.awayMessage
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else "Placeholder Text"
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input ref="master_toggle" type="checkbox" onChange={this.onToggleChange} defaultChecked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} defaultValue={awayMessageText} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
my console.log for AwayMessage shows the following:
AWAY_MESSAGE Object {}
AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false}
Another way of fixing this is by changing the key of the input.
<input ref="text" key={this.state.awayMessage ? 'notLoadedYet' : 'loaded'} onChange={this.onTextChange} defaultValue={awayMessageText} />
Update:
Since this get upvotes, I will have to say that you should properly have a disabled or readonly prop while the content is loading, so you don't decrease the ux experience.
And yea, it is most likely a hack, but it gets the job done.. ;-)
defaultValue is only for the initial load
If you want to initialize the input then you should use defaultValue, but if you want to use state to change the value then you need to use value. Personally I like to just use defaultValue if I'm just initializing it and then just use refs to get the value when I submit. There's more info on refs and inputs on the react docs, https://facebook.github.io/react/docs/forms.html and https://facebook.github.io/react/docs/working-with-the-browser.html.
Here's how I would rewrite your input:
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else ''
<input ref="text" onChange={this.onTextChange} placeholder="Placeholder Text" value={#state.awayMessageText} />
Also you don't want to pass placeholder text like you did because that will actually set the value to 'placeholder text'. You do still need to pass a blank value into the input because undefined and nil turns value into defaultValue essentially. https://facebook.github.io/react/tips/controlled-input-null-value.html.
getInitialState can't make api calls
You need to make api calls after getInitialState is run. For your case I would do it in componentDidMount. Follow this example, https://facebook.github.io/react/tips/initial-ajax.html.
I'd also recommend reading up on the component lifecycle with react. https://facebook.github.io/react/docs/component-specs.html.
Rewrite with modifications and loading state
Personally I don't like to do the whole if else then logic in the render and prefer to use 'loading' in my state and render a font awesome spinner before the form loads, http://fortawesome.github.io/Font-Awesome/examples/. Here's a rewrite to show you what I mean. If I messed up the ticks for cjsx, it's because I normally just use coffeescript like this, .
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
{ loading: true, awayMessage: {} }
componentDidMount: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message, loading: false
onToggleCheckbox: (event)->
#state.awayMessage.master_toggle = event.target.checked
#setState(awayMessage: #state.awayMessage)
onTextChange: (event) ->
#state.awayMessage.text = event.target.value
#setState(awayMessage: #state.awayMessage)
onSubmit: (e) ->
# Not sure what this is for. I'd be careful using globals like this
window.a = #
#submitAwayMessage(#state.awayMessage)
submitAwayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
if this.state.loading
`<i className="fa fa-spinner fa-spin"></i>`
else
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input type="checkbox" onChange={this.onToggleCheckbox} checked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} value={this.state.awayMessage.text} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
That should about cover it. Now that is one way to go about forms which uses state and value. You can also just use defaultValue instead of value and then use refs to get the values when you submit. If you go that route I would recommend you have an outer shell component (usually referred to as high order components) to fetch the data and then pass it to the form as props.
Overall I'd recommend reading the react docs all the way through and do some tutorials. There's lots of blogs out there and http://www.egghead.io had some good tutorials. I have some stuff on my site as well, http://www.openmindedinnovations.com.
it's extremely simple, make defaultValue and key the same:
<input defaultValue={myVal} key={myVal}/>
This is one of the recommended approaches at https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
To force the defaultValue to re-render all you need to do is change the key value of the input itself. here is how you do it.
<input
type="text"
key={myDynamicKey}
defaultValue={myDynamicDefaultValue}
placeholder="It works"/>
Maybe not the best solution, but I'd make a component like below so I can reuse it everywhere in my code. I wish it was already in react by default.
<MagicInput type="text" binding={[this, 'awayMessage.text']} />
The component may look like:
window.MagicInput = React.createClass
onChange: (e) ->
state = #props.binding[0].state
changeByArray state, #path(), e.target.value
#props.binding[0].setState state
path: ->
#props.binding[1].split('.')
getValue: ->
value = #props.binding[0].state
path = #path()
i = 0
while i < path.length
value = value[path[i]]
i++
value
render: ->
type = if #props.type then #props.type else 'input'
parent_state = #props.binding[0]
`<input
type={type}
onChange={this.onChange}
value={this.getValue()}
/>`
Where change by array is a function accessing hash by a path expressed by an array
changeByArray = (hash, array, newValue, idx) ->
idx = if _.isUndefined(idx) then 0 else idx
if idx == array.length - 1
hash[array[idx]] = newValue
else
changeByArray hash[array[idx]], array, newValue, ++idx
Related issue
Setting defaulValue on control din't not update the state.
Doing reverse works perfectly:
Set state to default value, and the control UI gets updated correctly as if defaulValue was given.
Code:
let defaultRole = "Owner";
const [role, setRole] = useState(defaultRole);
useEffect(() => {
setMsg(role);
});
const handleChange = (event) => {
setRole(event.target.value );
};
// ----
<TextField
label="Enter Role"
onChange={handleChange}
autoFocus
value={role}
/>
Define a state for your default value
Surround your input with a div and a key prop
Set the key value to the same value as the defaultValue of the input.
Call your setDefaultValue defined at the step 1 somewhere to re-render your component
Example:
const [defaultValue, setDefaultValue] = useState(initialValue);
useEffect(() => {
setDefaultValue(initialValue);
}, false)
return (
<div key={defaultValue}>
<input defaultValue={defaultValue} />
</div>
)
Give value to parameter "placeHolder".
For example :-
<input
type="text"
placeHolder="Search product name."
style={{border:'1px solid #c5c5c5', padding:font*0.005,cursor:'text'}}
value={this.state.productSearchText}
onChange={this.handleChangeProductSearchText}
/>
Use value instead of defaultValue and change the value of the input with the onChange method.