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.
Related
-Hello, I made a code to send props value from child component to parent component
in the child component I have multiple checkboxes with a value that I'll pass to the props, in addition of two functions, the first function to store the clicked inputs on array and pass it to the props and the second function to call the first function if the user check new input (this function used to update the props value )
export const ColorsForm = (props) => {
useEffect(()=>{
test()
},[])
// this function to store the checked colors in array then pass this array to the props
const getColors = () => {
const inpColor = document.querySelectorAll(".colorsInp");
let arrOfColors = [];
for(let i = 0; i < inpColor.length; i++){
if(inpColor[i].checked){
arrOfColors.push(inpColor[i].value);
}
}
props.array_of_colors(arrOfColors);
return arrOfColors;
}
// this function will be used to resend the prop value with new value
const whenNewColorClicked = () => {
const inpColor = document.querySelectorAll(".colorsInp");
for(let i = 0; i < inpColor.length; i++){
inpColor[i].addEventListener("click",()=>{
getColors();
})
}
}
return (
<div>
<label>Colors:</label>
<div>
<input type="checkbox" id="colorBlack" value="black" name='colorsInput' />
<label htmlFor="colorBlack" className='lblBlack'></label>
<input type="checkbox" id="colorWhite" value="white" name='colorsInput' />
<label htmlFor="colorWhite" className='lblWhite'></label>
<input type="checkbox" id="colorRed" value="red" name='colorsInput' />
<label htmlFor="colorRed" className='lblRed'></label>
<input type="checkbox" id="colorBlue" value="blue" name='colorsInput'/>
<label htmlFor="colorBlue" className='lblBlue'></label>
</div>
</div>
)
}
the parent component is where I'll valid the upcoming props to make a post request
import React, { useState ,useEffect } from 'react'
import { ColorsForm } from './colorsForm';
export const AddForm = () => {
const [colors, setColors] = useState([])
return (
<>
<ColorsForm array_of_colors={res => setColors(() => [...res])} />
<button onClick={postRequestFunction} >Insert</button>
</>
);
}
my code works fine but...
I know the child component code can be wrote better than I did, please feel free to notice anything, I'll be thankful
You do an "anti-pattern" here.
The parent must have the information and the children must display and interact with this information.
So the colors array must be define in the parent and your ColorsForm must .map the colors array props to display the different color in the form with the correct value.
Then, if you use document.querySelector is that there is a problem.
React manages the DOM and you should never do it for it. In your two functions, you should never listen to the click event by hand.
So I can't explain everything here, but I hope this information will help you improve your code. The best way to learn is to search and find answers by yourself
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>
)
}
Fails to run first if condition.
So once component is loaded it shows "Student Progress related Stats" and when once topic-link is set(in some other component which is removed too properly) it shows second display. Everything works fine till now but when topic-link is removed if fails to run first if condition and shows second display still. Basically I want to change my component view based on topic-link is there or not.
import React, { Component } from "react";
import {connect} from 'react-redux';
import {getQuestionsList} from '../../store/actions/questionActions';
class Test extends Component {
render(){
let display;
let topicLink = localStorage.getItem('topic-link');
if(!topicLink){
display =
<div style={{textAlign:'center',
fontSize:'22px'}}>
<p>Student Progress related Stats</p>
</div>
}
else if(topicLink){
display =
this.props.questions.map(question => (
<div key={question.id} style={{border:'1px solid #000',marginBottom:'15px'}}>
<div dangerouslySetInnerHTML={{__html: question.direction}} />
<div dangerouslySetInnerHTML={{__html: question.question}} />
<div>
<form>
<input type="radio" name="option" value="(A)"/>{question.option_a}<br/>
<input type="radio" name="option" value="(B)"/>{question.option_b}<br/>
<input type="radio" name="option" value="(C)"/>{question.option_c}<br/>
<input type="radio" name="option" value="(D)"/>{question.option_d}
</form>
</div>
</div>
))
}
return (
<div>
{display}
</div>
);
}
};
const mapStateToProps = state => {
return {
questions: state.questions.items,
}
}
export default connect(mapStateToProps, {getQuestionsList})(Test);
Here where i set the localStorage. On Every onTopicClick I get the desired output(meaning second if statement runs and it updates the component). Only when localStorage is removed it stays on the second if statement which not be the case(it should run first if statement). I have two fucntions(in seperate components) like onSectionClick and onTopicClick like this:
onTopicClick = () => {
this.props.getQuestionsList(this.props.topicId);
let topicName = this.props.name;
topicName = topicName.replace(/\s+/g, '-').toLowerCase();
let topicLink = "/updates/daily-practice-questions/" + this.props.sectionName + "/" + topicName;
this.props.history.push(topicLink);
localStorage.setItem('topic-link', topicLink);
}
onSectionClick = () => {
this.setState(prevState => ({
isOpened: !prevState.isOpened
}));
let sectionName = this.props.name;
sectionName = sectionName.replace(/\s+/g, '-').toLowerCase();
this.setState({sectionName: sectionName});
this.props.history.push("/updates/daily-practice-questions/" + sectionName);
if(this.state.isOpened){
this.props.history.push("/updates/daily-practice-questions");
localStorage.removeItem('topic-link')
}
}
One of the hardest topics in data communication probably.
let topicLink = localStorage.getItem('topic-link'); This line is the money line. Basically, you already identified the driver of this component, for instance, topicLink.
The next thing is to make sure this component use this variable as a prop input. I noticed you are using redux already, therefore this variable could be part of the state variables there.
Next step will be to see if your store can update this variable from localStorage. This probably is another topic, for instance, check the localStorage every 5 seconds, and then update the store variable.
However, it's not a good idea to sync components via a localStorage variable, instead, you should bootstrap your code and save this localStorage variable as a state variable first and store in your redux for example.
So either way, input prop is the first step to go, it'll help you test the code as well.
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
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});
}