Change a state in react - javascript

I'm learning React and as a learning exercise am trying to do a very basic page where there is a form and you put text in an input box, you click submit and the header changes to what you entered. Here is my code so far:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {header: 'yeaheheh'}
}
changeHeader(e) {
let newHeader = document.getElementById('input').value();
e.preventDefault();
console.log('submitted');
this.setState(newHeader);
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader.bind(this)} className="change-header-form">
<input id="input" type="text" placeholder="Enter Text Here" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
export default App;
At first, when I clicked submit, nothing happened and I got an error in the console that says
Uncaught TypeError: Cannot read property 'setState' of null
I then realized I needed to bind the changeHeader function to this which I changed so before I had:
<form onSubmit={this.changeHeader}...
changed it to
<form onSubmit={this.changeHeader.bind(this)}...
After doing this, the error cleared but my header is still not updating.I read that there has been strong suggestions against changing state via setState is bad practice because calling setState() again could potentially alter the changed state. setState is also an asynchronous operation which would also explain why my header isn't changing.
With all that said, then what would be the best way to handle this? From what I understand, props wouldn't make sense either since those values are stored directly in your component and aren't parameters that can't be dynamically updated. I'm having a hard time understanding the relationship between these different data types and how they are handled in the DOM.

You are setting state incorrectly.
More over to get the data from input fields you can either use controlled input elements(via states) or uncontrolled input elements via "ref" which I have used in below example.
In controlled input element you store the value of input element in state and changes to that value is done by calling onChange method and then setting the state via this.setState({}).
Calling setState causes re-rendering to happen and dom gets the updated data based on new state.
Btw "refs" gets you the direct access to dom elements, in similar way $() was used in jquery and should be avoided if possible because it will lead to very hard to manage and predict dom changes.
Also there are cases where use of "refs" is recommended
There are a few good use cases for refs:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
class App extends React.Component {
constructor() {
super();
this.state = {header: 'yeaheheh'};
}
changeHeader = (e) => {
e.preventDefault();
let newHeader = this.textInput.value;
console.log('submitted');
this.setState({header : newHeader});
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader} className="change-header-form">
<input id="input" ref={(input) => { this.textInput = input; }} type="text" placeholder="Enter Text Here" />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('test'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="test">
</div>

Replace this.setState(newHeader); with this.setState({header: newHeader});.

Take a look at this article in the react docs: https://facebook.github.io/react/docs/forms.html#controlled-components.
Basically what you want to do is create another handler for the input. This will be called every time there is a change to the input field and a property in your state will be updated. Then, when you submit the form you can take that new property and "merge" it using setState to become the new header.
JS Bin
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
header: 'yeaheheh',
next: ''
}
this.changeHeader = this.changeHeader.bind(this);
this.updateNext = this.updateNext.bind(this);
}
changeHeader(e) {
e.preventDefault();
this.setState({
header: this.state.next
});
}
updateNext(e) {
this.setState({
next: e.target.value
});
}
render() {
return (
<div>
<h1>{this.state.header}</h1>
<form onSubmit={this.changeHeader} className="change-header-form">
<input id="input" type="text" placeholder="Enter Text Here" onChange={this.updateNext} />
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
Maybe this bin will provide a little better context at what I'm trying to describe.

There's the small bug in your code preventing it from working (this.setState(newHeader) -> this.setState({header: newHeader});), but the thing is that your code is not idiomatic for React.
You are supposed to use controlled components instead of grabbing the values from the form's inputs on submit, as you would do with jQuery.
"Controlled component" is a silly name for the pattern where an input's state is mapped to the application state, so an input itself behaves as if it would be kinda "stateless". In your case, you need to have separate component state member for every text input you've got. Input control should look like this:
<input value={ this.state.inputValue }
onChange={ e => this.setState({ inputValue : e.target.value }) }
/>
Now it's bound to your inputValue state member, so you can just take it from the state at any moment you need. On form's submit handler, in your case.
That's it. Your code must be fixed accordingly. Refer to the "controlled components" manual for further details, it's the really important React concept.

You should modified your function like this..
constructor(props) {
super(props);
_that = this;
}
changeHeader = (e) => {
e.preventDefault();
let newHeader = this.textInput.value;
console.log('submitted');
_that.setState({header : newHeader});
}

Related

Passing writable store values from parent to child component via props in Svelte

I'm new to svelte and I'm trying to get a sense for best practices. I want to create a dynamic form component I can use throughout an app. Each instance of the form will have the same styling - but each form will be tied to different input values in the store, will have different submit logic, etc.
Here's a simple example (that doesn't work):
App.svelte
<script>
import store from "./store";
import Form from "./Form.svelte";
const { inputValueA, inputValueB } = store;
const handleSubmitA = () => {
alert($inputValueA);
};
const handleSubmitB = () => {
alert($inputValueB);
};
</script>
<div>
<Form
inputValue={$inputValueA}
labelText="form input a"
handleSubmit={handleSubmitA}
/>
<Form
inputValue={$inputValueB}
labelText="form input b"
handleSubmit={handleSubmitB}
/>
</div>
Form.svelte
<script>
export let handleSubmit = () => null;
export let inputValue = "";
export let labelText = "";
</script>
<form>
<label for="input">{labelText}</label>
<input id="input" type="text" value={inputValue} />
<button on:click|preventDefault={handleSubmit}>submit</button>
</form>
This example doesn't work, I don't believe the inputValue prop is bound to the text input correctly. Any folks out there with svelte experience willing to share how you'd approach this?
Here's a sandbox:
https://codesandbox.io/s/svelte-form-component-ujnwki
Let me know what you suggest! Thanks.
Not really sure what is exactly is your question but here is the fix for your example to make it work:
you have to bind the value to your component to be able to change it form the child
bind:inputValue={$inputValueA}
and bind the value to the input itself to make its value reactive
bind:value={inputValue}

QuerySelector vs .target

In the following code, how comes the const password uses .querySelector in the authorize() function? I am a bit confused because:
JSX isn't strictly Javascript, so how come you can even use .querySelector?
And secondly, in JavaScript, you'd just select input as this would represent the password input box, but in this case input[type="password"] has been selected. Is this because in HTML the input boxes are not named so you have to differentiate them?
If authorize() uses e.target, why is querySelector needed? won't authorize already realize the password input field is what is triggering the function? Or am I wrong and instead, the 'e' triggering authorize() is the form and so similar to document.querySelector, you then need to select the input field itself as this is a subtype of the form?
import React from 'react';
import ReactDOM from 'react-dom';
class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {
password: 'swordfish',
authorized: false
};
this.authorize = this.authorize.bind(this);
}
authorize(e) {
const password = **e.target.querySelector(
'input[type="password"]').value;**
const auth = (password == this.state.password);
this.setState({
authorized: auth
});
}
render() {
let login = (
**<form action="#" onSubmit={this.authorize}>**
<input type="password" placeholder="password"></input>
<input type="submit"></input>
</form>
);
let contactInfo = (
<ul>
<li>
client#example.com
</li>
<li>
555.555.5555
</li>
</ul>
);
return (
<div id="authorization">
<h1>{this.state.authorized ? 'Contact' : 'Enter the Password'}</h1>
<h1>{this.state.authorized ? contactInfo : login}</h1>
</div>
);
}
}
ReactDOM.render(
<Contact />,
document.getElementById('app')
);
Thanks!
JSX is extended syntax for JS. Adding more stuff doesn’t stop you using existing stuff.
There is no need to use a selector that specific in that piece of code. It does not harm and can aid readability.
The target of the submit event is the form, not the input. If you need to read the input, then you have to select it somehow. querySelector is a pretty common approach.
That said, this is not an idiomatic approach to writing React applications. See the documentation on forms for the accepted React approach.
Usually, you want to have controlled components in React and keep the input value inside state and let React be the single source of truth, not leave it to the DOM to decide what's what. A simple example, using hooks, would be:
import React from 'react';
const SimpleExample = () => {
// use a piece of state to track and hold the input value
const [inputVal, setInputVal] = useState("");
// change state on input change
const handleChange = event => setInputVal(event.target.value);
// react also handles submit
const handleSubmit = event => {
// prevent default browser behaviour
event.preventDefault();
// you can send values held inside state to the back-end here
console.log("Submitting...", inputVal)
}
return (
<form onSubmit={event => handleSubmit(event)}>
<input
type="password"
// the value of the input is held by React state
value={inputVal}
// change state something is being typed inside input
onChange={event => handleChange(event)}
/>
<button>Submit</button>
</form>
)
}

How to collect IDs of all checked checkboxes and push values to an array in React

I'm moving an old multiple choice quiz app from Blaze to React.
The app grabs quizzes from the DB and then for each question it loops through the answers and prints a checkbox for each one.
Each of these answers for each of the questions was inside a form, and when the form was submitted I used jQuery to grab the ID of each :checked checkbox. These IDs were then pushed to an array and sent to the server to compare vs the correct answers.
Now that I'm using React, I'm having some difficulty replicating this functionality as using checkboxes isn't the same.
What would be the best way to get the value of the checked checkboxes in to an array?
Here is my code with as much irrelevant data cut out as possible:
Assessment.jsx
class Assessment extends React.Component {
constructor(props) {
super(props);
}
render() {
const { module } = this.props.params;
const { loading, modules, assessment } = this.props;
return (
<div className="Assessment">
<div className="section">
<form onSubmit={ this.submitForm }>
{ assessment.questions.map((question) => {
return <AssessmentQuestion question={question} key={question.number}/>
})}
<button className="btn btn-action btn-block" type="submit">Submit Assessment</button>
</form>
</div>
</div>
)
}
}
AssessmentQuestion.jsx
class AssessmentQuestion extends React.Component {
constructor(props) {
super(props);
}
render() {
const { question } = this.props;
return (
<div className="question" data-number={question.number}>
<p>
{question.number}) {question.question}
</p>
{ question.answers.map((answer) => {
return <div className="checkbox">
<label>
<input type="checkbox" name={question.number} value={answer.letter} id={answer.number, answer.letter}/>
{ answer.answer }
</label>
</div>/>
})}
</div>
)
}
}
As you can see, I am looping through each question and for each question looping through each answer and printing a checkbox. When the user submits the form in the parent 'Assessment.jsx' component, I want to collect the id of each checked checkbox and push that in to an array so I can send to the server for grading.
Any help much appreciated
There are a couple of ways you could solve this. The easier way, which is closer to the solution you had before, is to use React refs.
https://facebook.github.io/react/docs/refs-and-the-dom.html
You could add a ref to the checkboxes, and then filter down to the checked ones.
An alternative (and more "Reacty" approach) would be to manage all this "checked" logic with React state. Basically, your Assesment.jsx class would keep track of the state and then send it to the form on submit.
You create a function to update the state with the questionName and answer, and pass that function as an onClick callback to your AssesmentQuestion class.
Each checkbox would have onclik which would update state, or even better if using redux, dispatch action which would result in state update.
When sending to server only information in state would get used

Multiple state changes in react

Suppose I have a settings screen on my React app, with three settings
enable thingie
use doohickey
activate gizmo
What's the best way to handle all the onchange events from all these controls? Here's what I have at the minute
import React from 'react';
import Modal from 'react-bootstrap-modal';
 
export default class Settings extends React.Component {
constructor (props) {
super(props)
this.state = {
open: false
}
this.handleThingieChange = this.handleThingieChange.bind(this);
this.handleDoohickeyChange = this.handleDoohickeyChange.bind(this);
this.handleGizmoChange = this.handleGizmoChange.bind(this);
}
handleThingieChange(e) {
this.props.onThingieChange(e.target.value)
}
handleDoohickeyChange(e) {
this.props.onDoohickeyChange(e.target.value);
}
handleGizmoChange(e) {
this.props.onGizmoChange(e.target.checked);
}
  render() {
    return (
      <div>
<label>enable thingie</label>
<input type="checkbox" checked={this.props.thingie} onChange={this.handleThingieChange} />
<label>use doohickey</label>
<input type="checkbox" checked={this.props.doohickey} onChange={this.handleDoohickeyChange} />
<label>activate gizmo</label>
<input type="checkbox" checked={this.props.gizmo} onChange={this.handleGizmoChange} />
      </div>
    )
  }
}
And in my <App> component:
thingieChange(thingie){
this.setState({ thingie: thingie })
}
doohickeyChange(doohickey){
this.setState({ doohickey: doohickey })
}
gizmoChange(gizmo){
this.setState({ gizmo: gizmo })
}
// render method is most important
// render method returns JSX template
render() {
return (
<form>
<h2>headerooney</h2>
<Settings
onThingieChange={this.thingieChange.bind(this)}
onDoohickeyChange={this.doohickeyChange.bind(this)}
onGizmoChange={this.gizmoChange.bind(this)} />
</form>
);
}
My app could potentially have 20 or 30 settings, it seems like there must be a better way to do this than by repeating this pattern 20-30 times?
I'm new to react, if I was building this with jquery I'd do something like this:
  <div>
<label>enable thingie</label>
<input type="checkbox" data-property='thingie' />
<label>use doohickey</label>
<input type="checkbox" data-property='doohickey' />
<label>activate gizmo</label>
<input type="checkbox" data-property='gizmo' />
</div>
with a generic click handler like this
$(function(){
$.each(options, function(k, v) {
$("[data-property='" + k + "']")[0].checked = v;
});
$(document).on("click", "input[type='checkbox']", function(){
var propertyName = $(this).data("property");
options[propertyName] = $(this)[0].checked;
console.log(options);
})
});
How do I do something equivalent in react? I don't want to spend two hours writing a handler function for every single setting
If all of your controls are just checkboxes then why not create a single component for a setting and then have a Settings component that lists them all.
You could provide the name of the setting as a property for the component so that it knows what it was updating. You could pass a function from the Settings component to the Setting component that provided the means to update state.
One more complex option for addressing this would be to use Redux but it is probably not necessary for something this straightforward (although it would provide plenty of benefit in terms of simplifying testing, etc).
I would suggest though that as soon as you seen any kind of repeating pattern that you try to abstract it to a reusable component - it will definitely save you time in the long run.

How to get Material-UI's <TextField/> to return correctly with ref='' like <input/> in ReactJS?

With the following method:
handleClick(event) {
const inputText = this.refs.inputText
console.log(inputText.value.trim())
}
I am trying to get Material-UI's <TextField/> to return the input text correctly with ref like the <input/> can with <button/> triggering it:
<input
className='form-control'
placeholder='Input Text'
ref='inputText'
type='text'
/>
<button
onClick={(event) => this.handleClick(event)}
>
And I attempted the following with <TextField/>, but it returns as undefined. How can I get it to return inputted text correctly like the <input/> above?
<TextField
hint='Enter text'
className='form-control'
ref='inputText'
type='text'
/>
I would suggest this approach:
Set up your textfield with a value and onChange function that are hooked into redux itself, where the onChange function just updates the value.
So you'd have something like this :
<TextField
value={this.props.textFieldValue}
onChange={this.props.textFieldChange}
Where the textFieldChange is an action that simply updates the textFieldValue. Most forms in redux will work something like this. Keep in mind the names i made up for those props and action are just for example. If you have a big form you might want to consider have part of the state tree dedicated to the form itself where you have :
state: {
form: {
textField: ...your textfield value here,
name: ...,
whateverElse: ...
}
};
I like doing this with redux because I can make that architect form part of the state to look like the json payload of wherever I'm sending it to, so there I can just send the form went I want to send it.
Anyways, back to this example. When you click your handleClick now. All you need to do is this to get the value:
handleClick(event) {
console.log(this.props.textFieldValue.trim());
}
Because the textfield is updated with every change, you always have access to it in your state. This also gives you flexibility over the refs approach, because if you use refs you will have a lot harder of a time getting access to that form in other components. With this approach, all the information is on your state so you can access it anytime, as long as you manage your props.
You should use the onChange={} to get the value:
_onChange = (e) => {
console.log(e.target.value);
}
<TextField
onChange={this._onChange}
/>
Here's a better solution than using onchange event, we get directly the value of the input created by material-ui textField :
create(e) {
e.preventDefault();
let name = this.refs.inputText.input.value;
alert(name);
}
constructor(){
super();
this.create = this.create.bind(this);
}
render() {
return (
<form>
<TextField ref="inputText" hintText="" floatingLabelText="Your name" /><br/>
<RaisedButton label="Create" onClick={this.create} primary={true} />
</form>
)}
hope this helps.

Categories