ReactJS Radio Buttons - reactjs

I am having trouble rendering information depending on the selected radio button. What should happen is when the yes button is clicked, 'Welcome user' should appear. When no is selected, 'Please login' should appear.
I am also having trouble with the states. When I click a button initially, the console button shows my state as blank. Then when I click no, the state updates as yes and vise versa. Not sure what is going on here.
render() {
return (
<form onSubmit={this.onFormSubmit}>
<p>Did you go to the lecture?</p>
<label>
<input
type="radio"
value="No"
checked={this.state.goToLectue === 'No'}
onChange={this.handleChange}
/>
No
</label>
<label>
<input
type="radio"
value= "Yes"
checked={this.state.goToLectue === 'Yes'}
onChange={this.handleChange}
/>
Yes
</label>
<div>
<Response goToLecture = {this.state.goToLectue}/>
</div>
);
}
onFormSubmit = e => {
e.preventDefault();
console.log('Chosen: ' + this.state.goToLectue);
};
handleChange = e => {
this.setState({
goToLectue: e.target.value
});
console.log('value of goToLectue onchange: ' + this.state.goToLectue);
}
function outside component
function Response(props) {
if (props.goToLectue)
return <h1>Welcome User</h1>;
else
return <h1>Please Login</h1>;
}

I can see two problems in your code:
1) Response component expects a boolean in its props.goToLectue property, but you're passing it a string ('Yes' or 'No'). Either change state.goToLectue of the main component to also be boolean, or you can do something like <Response goToLecture = {this.state.goToLectue === 'Yes'}/>.
2) In your handleChange method you're logging this.state.goToLectue - this value is stale, you won't see the effect of the setState call immediately but only on the next render() call. You should be logging e.target.value instead.
Hope that helps.

I can see a number of issues.
I have corrected them for you in a Codepen:
https://codepen.io/anon/pen/YRWWEQ
In Response the if statement is being passed the props.goToLecture which can only be 'Yes' or 'No', both these string will resolve as truthy. I added === 'Yes' to correct this. Tip for future is to stick to a boolean value wherever possible as it makes conditional statements a lot neater.
There seemed to be a mismatch of lecture and lectue in the code. I'm guessing the latter was a typo.
<form> tag was missing it's </form>
At the initial render the goToLecture is not set in the state and so it was throwing errors, this might be why you were seeing the blank screen. I added the default state as undefined to allow you the option of showing neither 'response' until a radio button is clicked if you choose.
Hope this helps!

Related

How is React handling renders for this uncontrolled input element?

I'm going through React's beta docs and would like to better understand how react handles user's interactions with the UI. Specifically with this non-controlled input field.
Note:
the input field is not disabled
new input values should not trigger a render, because it's not changing any state (local variables are ignored)
So, then why is the change not reflected in the UI???
The code is bellow, and an interactive "sandbox" can be found here: https://beta.reactjs.org/learn/state-a-components-memory under challenge #2 at the bottom of the page.
export default function Form() {
let firstName = '';
let lastName = '';
function handleFirstNameChange(e) {
firstName = e.target.value;
}
function handleLastNameChange(e) {
lastName = e.target.value;
}
function handleReset() {
firstName = '';
lastName = '';
}
return (
<form onSubmit={e => e.preventDefault()}>
<input
placeholder="First name"
value={firstName}
onChange={handleFirstNameChange}
/>
<input
placeholder="Last name"
value={lastName}
onChange={handleLastNameChange}
/>
<h1>Hi, {firstName} {lastName}</h1>
<button onClick={handleReset}>Reset</button>
</form>
);
}
To be clear, I'm not asking how to solve this problem; I know how to hook it up and fix it with state. I am interested in what's preventing the input field from updating with a new value.
On initial render it receives the empty string as its value. But then why doesn't it change when a user types?
Here's two non-react input fields, one with a value attribute, and one where the value is set by JS. Both can be modified:
<input type="text" value="editable">
<input type="text" id="input2">
<script>
document.getElementById('input2').value = "also editable"
</script>
So, why isn't the input editable in react?
There are two reasons for a component to render:
It’s the component’s initial render.
The component’s (or one of its ancestors’) state has been updated.
In your example, the second condition is not met and react has no way of knowing your input values changed, it does not re render, and so does not commit changes to the DOM.
Note that react is wrapping event handlers, it does not work exactly like vanilla JavaScript

Why does the component state get updated but the visuals aren't updated?

While debugging I confirmed the state is changing but the radio buttons aren't updating visually. Weird interaction: When I include an alert to be executed after the state has been changed, the correct radio button becomes the active selection but as soon as I exit the alert it reverts back even though the state hadn't changed.
constructor(props) {
super(props);
this.state = {
algorithmType: 'A*'
}
}
radioUpdated = (event) => {
this.setState({algorithmType: event.currentTarget.value});
}
<form>
<input type='radio'
id='PathFinding_RadioButton_Dijkstras'
name='algorithm'
value='Dijkstras'
checked={this.state.algorithmType == 'Dijkstras' && true}
onChange={this.radioUpdated}></input>
<label for="Dijkstras">Dijkstra's</label><br></br>
<input type='radio'
id='PathFinding_RadioButton_A*'
name='algorithm'
value='A*'
checked={this.state.algorithmType == 'A*'}
onChange={this.radioUpdated}></input>
<label for='A*'>A*</label><br></br>
<input type='radio'
id='PathFinding_RadioButton_D*'
name='algorithm'
value='D*'
onChange={this.radioUpdated}
checked={this.state.algorithmType == 'D*'}
></input>
<label for='D*'>D*</label>
</form>
I'm not sure if you made a mistake in copying and pasting the code, your class component is missing its render function.
Apart from that your code seems to work fine, here is working example:
https://codesandbox.io/s/mystifying-gagarin-dh6nz?file=/src/App.js
And there is version of the same component, but using hooks instead of a class:
https://codesandbox.io/s/nostalgic-dew-bhy7n?file=/src/App.js

How do i default check a radio button in react?

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

A single onChange listener on a <form> tag

I was just playing a bit with a more generic way of dealing with form data, and figured that setting a single onChange listener on the wrapping form tag should work for all changes to input fields.
And indeed, it does - no surprise there. Events get called, I know what changed by inspecting event.target.name and can update state accordingly. Fully as expected.
However, React doesn't seem to like it and spams the well known "You provided a value prop to a form field without an onChange handler" console warning.
Would there be any other reason for not doing this, apart from the console warning? It seems to eliminate a lot of duplication React otherwise gets criticised about.
class App extends Component {
state = {
name: 'Default Name',
number: 12,
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
})
}
render() {
const { crashLogo } = this.props;
return (
<div className="App">
<form onChange={this.handleChange}>
<input type="text" name="name" value={this.state.name} />
<input type="number" name="number" value={this.state.number} />
</form>
</div>
);
}
}
Just for clarity: I'm not asking for alternative solutions, I know I can set the same change listener directly on every input, use redux-form, own wrapper components with context magic and so on...

What is the difference betwen value and defaultValue in react.js [duplicate]

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.

Resources