I have a React form. I'm using the react-dropdown dependency. I'm having a very strange issue. There is an onChange prop passed to the dropdown component. When is comes back, it sends the value from the dropdown menu back up to the parent component (the form in my case).
One would THINK you could take that response and set its value to the state via this.setState().
Except when I use setState(), in any way, the display value for the select menu stops changing. It shows me my Select your Business text instead of the selected value. If I remove the setState(), it changes.
What.....?
Here is a trimmed down version of the component:
import React from 'react';
import Dropdown from 'react-dropdown'
import FormInput from '../FormInput/FormInput';
import FormCheckbox from '../FormCheckbox/FormCheckbox';
import './RegistrationForm.css'
import 'react-dropdown/style.css'
export default class RegistrationForm extends React.Component {
constructor(props) {
super(props);
this.state={
error_business_name: false,
error_business_email: false,
error_username: false,
error_password: false,
error_type: false,
error_terms: false,
error_policy: false,
email: null,
business_name: null,
username: null,
password: null,
website: null,
terms: false
}
}
handleSelect(e) {
console.log(e.value)
this.setState({ type: e.value })
}
render() {
return (
<main role="main" className="RegistrationForm">
<img alt="Simplr Logo" src={require("../../images/logo.png")} />
<form onSubmit={e => this.handleSubmit(e)}>
<section className={this.state.error_type ? "error" : ""}>
<label htmlFor="type">Type of Business</label>
<Dropdown
className={this.state.error_type ? "dropdown error-dropdown" : "dropdown"}
options={["Law Office", "Accounting Firm", "Construction"]}
// onChange={e => this.setState({ type: e.value })}
onChange={e => this.handleSelect(e)}
placeholder="Select your Business" id="type"
/>
<p className="error-message">Please select a valid business type</p>
</section>
<button>REGISTER</button>
</form>
</main>
)
}
}
handleSelect() gets called on change. If I remove this.setState({ type: e.value }) from that method, the display changes. But if I put it in, I can still get the value, but the display then won't change from the default Select your Business text. The value gets set to the state, but it doesn't appear to be selected to the user.
I have no idea how these two processes are even connected. To my mind, once things are sent off to handleSelect(), the dropdown's job is over. But clearly, the setState() part is impacting the dropdown.
Help!
React dropdown component needs an array of objects as options [{label : "", value : ""}]
So instead of passing a string array pass an array of objects and set whole selected object as state and assign the selected state to value in Dropdown.
Related
I have two react select dropdown and I want the second dropdown to reset and show the placeholder text. I'm setting the stet variable to null when the first dropdown changes. I'm getting the value as null in the backend code, but the value on the ui is not removed. Can some on please help me on how to reset the value in second dropdown and show the placeholder text.
I have some call back functions which will set the options for the two dropdowns. So, please ignore those for now.
import React, {Component} from 'react'
Import Select from 'react-select'
Class test extends Component{
constructor(props){
super(props)
this.state={
val1= null,
val1_options=[],
val2= null,
val2_options=[]
}
this.handleVal1Change = this.handleVal1Change.bind(this)
this.handleVal2Change = this.handleVal2Change.bind(this)
}
}
handleVal1Change(value1) {
this.setState({
val1: value1.value,
val2: null,
val2_options: null
})
}
handleVal2Change(value2) {
this.setState({
val2: value2.value
})
}
render() {
return (
<div>
<Select
placeholder='select val1'
options={this.state.val1_options}
onChange={this.handleVal1Change}
/>
</div>
<div>
<Select
placeholder='select val2'
defaultValue={this.state.val2}
options={this.state.val2_options}
onChange={this.handleVal2Change}
/>
</div>
)
}
export default test
You are using the Select components in uncontrolled mode, which means that the components are not connected to this.state.val1 or this.state.val2. The defaultValue you set for the second component is just used as an initial value. When this.state.val2 is changed in the first component, the second component will not notice the change.
Use value instead of defaultValue:
<Select
placeholder='select val2'
value={this.state.val2}
options={this.state.val2_options}
onChange={this.handleVal2Change}
/>
Given I have this code (I removed a lot of the select items so it wasn't a massive list but there would be an extra 20 or so):
import * as React from "react";
import { Form, Card, Grid } from "tabler-react";
import { Button } from "semantic-ui-react";
class Skills extends React.Component {
constructor(props) {
super(props);
this.state = {
showSaveButton: false,
showCancelButton: false,
};
}
onChange = (event) => {
this.setState(
{
showSaveButton: true,
showCancelButton: true,
});
}
cancelChanges = () => {
this.setState(
{
showSaveButton: false,
showCancelButton: false,
});
}
render() {
return (
<div className="card" name="skills">
<Card.Body>
<Card.Title>Top skills</Card.Title>
<Grid.Row>
<Grid.Col offset={1} md={10}>
<Form.Group name="softskills" label="Soft Skills">
<Form.SelectGroup canSelectMultiple pills onChange={this.onChange}>
<Form.SelectGroupItem
label="Communication"
name="communication"
value="Communication"
/>
<Form.SelectGroupItem
label="Teamwork"
name="teamwork"
value="Teamwork"
/>
</Form.SelectGroup>
</Form.Group>
</Grid.Col>
</Grid.Row>
<Button content='Cancel changes' floated='left' color='red' basic hidden={this.state.showCancelButton ? '' : 'hidden'} onClick={this.cancelChanges}/>
<Button content='Save changes' floated='right' color='green' basic hidden={this.state.showSaveButton ? '' : 'hidden'}/>
</Card.Body>
</div>
);
}
}
export default Skills;
The current functionality is that on change, 2 buttons will appear that are cancel or accept.
I need the below functionality but I can't work out how to do it unless I have like 60+ states (an initial and a working state for each option) which seems ridiculous:
The initial state is pulled from a database in a JSON array whereby everything that appears in that array should start out as selected (checked=true). For example, if the array is ["communication", "timemanagement"] I need to set the Communication and Time Management options to checked=true.
The initial state needs to be saved so that if anything changes and then the user clicks cancel, the checked boolean for each option is reset to what it was originally
If accept is clicked, the information needs to be sent to the database and so it needs to know what options have checked=true and be able to grab their names
So is there a way to do this without having a massive amount of states?
What you can do is create a mapping in state for all 60. When you get the results from the database, store them in state with fields to track checked and changed statuses:
// isDirty is a flag to say there are pending changes on the option
const options = arrayFromDatabase.map(arrayElement => ({ name: arrayElement, checked: true, isDirty: false })
then store that array in your state, e.g.,
this.setState({...this.state, options })
When a change is made, mark the specific option as dirty -> isDirty = true. If it's cancelled, flip the flag back to false -> isDirty = false.
Should look something like,
this.setState({
...state,
options: this.state.map(
option => option.name === valueToChange ? {
...option,
isDirty: true | false } :
option
)
})
Handle your check-changed in the same way.
I have a modal that I'm using to allow editing of individual pieces of a page with a lot of discrete sections. This is a lot more user-friendly than passing them to a form - the form would be enormous.
The sections across the page vary though. Some are simple text and a simple textarea or input will suffice. Some data though can only be edited with a select (or conceivably multiple selects).
For the textareas, I am using the following:
/* inplace-edit dialog */
const [dialog, setDialog] = useState({
open: false, // whether dialog should show
fieldName: null, // reference to field for db update on submit
title: '', // dialog title
content: '', // data to show in dialog content
})
const setDialogState = update => () => {
setDialog({ ...dialog, ...update })
}
As a functional component is essentially a function, is it viable to add that component to the state and then use that component to render the specific form structure when the dialog needs to show?
I've done some more investigation and it seems viable to add a stateless component to state using hooks.
I've modified the state handling code to:
const [dialog, setDialog] = useState({
open: false,
title: '',
formComponent: () => null,
})
const setDialogState = update => () => {
setDialog({ ...dialog, ...update })
}
In the above formComponent is just a default function that returns null.
In a page section that I want to edit, there is a boolean showEditIcons that shows the edit icon if the viewer has appropriate permissions. On clicking the icon, it sets the state. And most importantly, it sets formComponent as a reference to the stateless function:
{showEditIcons && (
<IconButton
onClick={setDialogState({
open: true,
title: 'Project Summary',
formComponent: TextForm,
})}
>
<EditIcon />
</IconButton>
)}
where TextForm is just a function:
const TextForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="content">{({ field }) => <TextArea field={field} />}</Field>
</form>
)
I don't see any issue with assigning a function as an object property. Happens all the time.
The real interesting part of course is how I use TextForm. I pass the dialog values as props to a Dialog component:
<Dialog {...dialog} />
And in the part of the Dialog where I need the form, I use the TextForm to render the form with createElement
<DialogHeader>{title}</DialogHeader>
<DialogContent>
{React.createElement(formComponent)}
</DialogContent>
<DialogFooter ... />
This is an assignment for school. It involves using React and JSX to create an input field and a submit button. When the button's clicked, the input value should render as an element to the body. I was able to create it for the first click, but don't know how to repeat it.
If you look at the code below, you'll see that when user types, handleChange changes state of input and when the button's clicked, handleClick changes the boolean state of the button (called 'post'). If post is true, the input along with a timestamp is rendered as a heading.
The problem is that after the render, the input isn't cleared. If the user changes input and clicks button again, it updates the heading with a new timestamp and new input instead of adding another heading.
I've tried changing back state for input and post in handleClick, handleChange, componentDidMount, and componentDidUpdate. But that repeatedly calls setState and I get an error message 'maximum update depth exceeded.'
So, what I want it to do is post a new heading of the input value every time the user clicks the post button. I also want it to clear the input/placeholder text.
import React, { Component } from 'react';
import './App.css';
import Firstposts from './firstposts.jsx';
class App extends Component {
constructor(props) {
super(props)
this.state = {
input: "",
post: false
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleChange(event) {
this.setState({ input: event.target.value });
}
handleClick() {
this.setState({
post: true
})
}
render() {
let timestamp = new Date();
return (
<div className="container">
<div className="panel">
<img height="100 px" src="https://marketing.twitter.com/content/dam/marketing-twitter/brand/logo.png" alt=""></img>
<h1>Chirper</h1>
</div>
<div className="body">
<input
placeholder="type your message here"
onChange={this.handleChange}
/>
<button
onClick={this.handleClick}
>Post</button>
<h2>Log</h2>
{<Firstposts />}
{this.state.post ?
<div>
<h3>{timestamp.toString()}</h3>
<h4>{this.state.input}</h4>
</div>
:
<div />
}
</div>
</div >
);
}
}
export default App;
Update your handleClick method to set posts to an array of posts, instead of a boolean:
handleClick() {
this.setState({
posts: [
...this.state.posts,
this.state.input
]
})
}
This will add the value of this.state.input to the end of this.state.posts, preserving all previous posts.
You can update this further to clear the value of the input field:
handleClick() {
this.setState({
posts: [
...this.state.posts,
this.state.input
],
input: '' // add this line to clear your input field when a new post is submitted
})
}
Also, make sure to give your <input> element a value of this.state.input:
<input
value={this.state.input}
placeholder="type your message here"
onChange={this.handleChange}
/>
Without this, you will not be able to programmatically update the value of the <input> field by using setState. You can read more on uncontrolled components in React.
Then, update your render method to map over this.state.posts and render each one:
{this.state.posts.map(post => (
<h4>{post}</h4>
))}
You can read more on rendering lists in React.
A client wants to have a numeric slider component that lets him select a number, and a checkbox that says 'no value'. When the checkbox is checked, the slider should be disabled but should still retain its previously selected configuration.
I'm having trouble implementing this using the idiomatic React way, of Components that have props and state.
I'm envisioning the two as part of a single component, a slider-checkbox, with props like this:
value : number
onChange : (newValue : number) => void
Now, if the no value checkbox is checked, then value is undefined. The problem is that because value also sets the position of the slider, this means the position will be changed once the checkbox is ticked -- and I don't want that to happen.
The problem is while React wants to keep all the components in sync, in this case we don't want to keep the slider in sync with the rest of the application. Even though the value should be undefined (or null), we still want the slider to be sitting on the last value the user picked.
What is the correct way to resolve this issue?
I don't know how your application logic works, since you haven't shared it, however you could use shouldComponentUpdate() to resolve this issue.
If shouldComponentUpdate returns false, then render() will be completely skipped until the next state change. In addition, componentWillUpdate and componentDidUpdate will not be called.
So you could do:
shouldComponentUpdate(nextProps) {
if(typeof nextProps.value === 'undefined') return false; //return false if value is undefined
return true; //otherwise always return true (this is default)
}
This will prevent the component from re-rendering if value is undefined. Though I would suggest sending in null instead, since the variable is actually defined, but has simply no value.
Note that this will not disable the slider as you requested - it will simply maintain the original render-state of the slider.
If you also want to disable the slider when the checkbox is checked, you have a couple of options here as I see it. Here's one:
Split the component into 2 sub-components
The idea here is that the props control the slider and checkbox directly, without the use of a state. This is great if you want to use Stateless Functional Components, check out my guide on how and why to use that.
Demo
So your code would be something like:
CheckboxSlider:
class CheckboxSlider extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
value: 50, //default is in the middle, 50%
disabled: false
};
}
_toggleSlider = () => {
this.setState({disabled: !this.state.disabled});
}
render() {
<div>
<Slider value={this.state.value} disabled={this.state.disabled} />
<CheckBox onCheckboxChange={this._toggleSlider} />
</div>
}
}
export default CheckboxSlider;
Slider:
import React from 'react';
const Slider = (props) => (
<input id="slider" type="range" min="0" max="100" disabled={props.disabled} />
);
export default Slider;
Checkbox:
import React from 'react';
const Checkbox = (props) => (
<input id="checkbox" type="checkbox" onChange={props.onCheckboxChange} />
);
export default Checkbox;
Use separate state to have value of slider input, to enable/disable the slider and to check/uncheck the checkbox. Below is a sample snippet.
class Slider extends React.Component {
constructor(props) {
super(props)
this.state = {
sliderValue: 100,
checkedValue: false,
enable: false
}
}
handleChange= (e) => {
this.setState({sliderValue: e.target.value});
}
handleClick = (e) => {
if(this.state.checkedValue == false) {
this.setState({checkedValue: true, enable: true}, function() {
this.refs.slider.disabled = true;
}.bind(this));
} else if(this.state.checkedValue == true) {
this.setState({checkedValue: false, enable: false});
}
}
render() {
return (
<div>
<input type="range" ref="slider" value={this.state.sliderValue} onChange={this.handleChange} min="100" max="500" step="10" disabled={this.state.enable}/>
<input type="checkbox" onChange={this.handleClick} />
</div>
)
}
}
ReactDOM.render(<Slider />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="app"></div>