I am using react-select and just notice that when I already have value in the input field (either by user type or by choosing the option in menu list), then I blur the input then focus at it again - trying to edit my last input - its just start over from the beginning, not continue from the last character of the input. I just found this issue in the author's github. Its been raised from 2 years ago and still an open issue. Is there really no workaround to achieve this?
I recommend you to use controlled props inputValue and value pair with onChange and onInputChange like the following code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
value: ""
};
}
onInputChange = (option, { action }) => {
console.log(option, action);
if (action === "input-change") {
const optionLength = option.length;
const inputValue = this.state.inputValue;
const inputValueLength = inputValue.length;
const newInputValue =
optionLength < inputValueLength
? option
: this.state.inputValue + option[option.length - 1];
this.setState({
inputValue: newInputValue
});
}
};
onChange = option => {
this.setState({
value: option
});
};
render() {
return (
<div className="App">
<Select
options={options}
onChange={this.onChange}
onInputChange={this.onInputChange}
inputValue={this.state.inputValue}
value={this.state.value}
/>
</div>
);
}
}
In react-select v1 the props onSelectResetsInput set to false was doing this job but I guess for v2 you will need to do the trick.
Here a live example.
Related
I have an app, let's call it Stopwatch so you know how it is working.
First, it have second meter which you can pause by pressing stop (stop button appears when pressing start), then you are able to start it again from the paused place or reset and start over.
If i use submit method in the form for saving time value and title, it works fine, but it reset time counter also and make stupid "white flash" after submitting. So i cant use it.
Problem of the case: I made button outside of the form, and it works almost fine. I want clear input with that button also. I tried these inside of the button function, but not working:
document.getElementById("title-value").reset();
document.getElementById("title-value").value = '';
Here is my code (and picture of app below):
import React from 'react';
class StopwatchHistory extends React.Component {
constructor(props) {
super(props);
this.state = {
history: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) { this.setState({value: event.target.value}); }
handleSubmit(event) {
event.preventDefault();
}
componentDidMount() {
this.setHistoryState();
}
setHistoryState = () => {
if (localStorage.times) {
this.setState({ history: localStorage.times.split('|') });
} else {
this.setState({ history: [] });
}
};
saveToLocalStorage = () => {
let titletieto = `${this.state.value}`; // form input value
// printing title and time to appearing list
if (localStorage.times) {
localStorage.times =
`${titletieto} ${this.props.formatTime(
this.props.currentTimeMin
)}:${this.props.formatTime(
this.props.currentTimeSec
)}:${this.props.formatTime(this.props.currentTimeMs, 'ms')}|` +
localStorage.times;
} else {
localStorage.times = `${titletieto} ${this.props.formatTime(
this.props.currentTimeMin
)}:${this.props.formatTime(
this.props.currentTimeSec
)}:${this.props.formatTime(this.props.currentTimeMs, 'ms')}|`;
}
};
saveTime = () => {
if (typeof Storage !== 'undefined') {
this.saveToLocalStorage();
} else {
console.error('local storage not supported');
}
this.setHistoryState();
};
// Remove times from Local Storage
resetHistory = () => {
if (localStorage.times) {
localStorage.removeItem('times');
}
this.setHistoryState();
};
render() {
return (
<div className={'stopwatch__history'}>
<div className="container">
<form id="title-value" onSubmit={this.handleSubmit}>
<label>
<input type="text" value={this.state.value} onChange={this.handleChange} /> </label>
</form>
</div>
<button onClick={this.saveTime}>SAVE TIME</button>
<button onClick={this.resetHistory}>RESET HISTORY</button>
<h3>History</h3>
<ul>
{this.state.history.map((item, index) => <li key={index}>{item}</li>)}
</ul>
</div>
);
}
}
export default StopwatchHistory;
Your input is controlled input. You have value set to be value={this.state.value} and handle change function is:
handleChange(event) { this.setState({value: event.target.value}); }
so if you want to reset this input, your reset function would simply set that state.value to be an empty string.
reset = () => {
this.setState({value: ""});
};
If your input would be uncontrolled (so value and onChange would not be provided) you could change it by accessing it through Ref. (see React.createRef).
i am using creatable select where i want to hide "create new" menu option. here is my
CodeSandbox i tried following but no luck promptTextCreator={() => false}
thanks you and appreciate any help
// try this way
return (
<CreatableSelect
isClearable
onChange={this.handleChange}
onInputChange={this.handleInputChange}
options={colourOptions}
noOptionsMessage={() => null}
// isValidNewOption={() => true}
// or `isValidNewOption={() => false}`
promptTextCreator={() => false}
/>
);
If you want to hide the create new value message at all times while still being able to create new values, you have to use the prop formatCreateLabel as follows formatCreateLabel={() => undefined} when you define your CreatableSelect.
Disabling create label via formatCreateLabel={() => undefined} is the right direction but the menu list sill shows empty space instead of not showing at all which is what you may prefer.
You may want to hide the menu list completely when there is no option by setting the menu list display to none
// Remember to define a unique id for your component in the constructor
// so you can target the right menu list element to hide it
id = "";
constructor(props) {
super(props);
this.id = "react-select_" + Math.random().toFixed(8).slice(2);
}
handleInputChange = (inputValue: any, actionMeta: any) => {
setTimeout(() => {
const menuEl = document.querySelector(`#${this.id} [class*="-menu"]`);
const menuListEl = document.querySelector(
`#${this.id} [class*="MenuList"]`
);
if (
menuListEl.children.length === 1 &&
menuListEl.children[0].innerHTML === ""
) {
menuEl.style.display = "none";
} else {
menuEl.style.display = "block";
}
});
};
...
<CreatableSelect
id={this.id}
onInputChange={this.handleInputChange}
formatCreateLabel={() => undefined}
...
/>
Live Demo
Just add these props:
menuIsOpen={false} and components={{ DropdownIndicator: null }}
Then handle onKeyDown and onInputChange event as explained in => https://react-select.com/creatable, have a look into "Multi-select text input" section
Here is the complete example:
import React, { Component } from 'react';
import CreatableSelect from 'react-select/creatable';
const components = {
DropdownIndicator: null,
};
const createOption = (label: string) => ({
label,
value: label,
});
export default class CreatableInputOnly extends Component<*, State> {
state = {
inputValue: '',
value: [],
};
handleChange = (value: any, actionMeta: any) => {
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)],
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<CreatableSelect
components={components}
inputValue={inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Type something and press enter..."
value={value}
/>
);
}
}
Just add
<CreatableSelect
components={{
...components,
Menu: () => null
}}
/>
Just treat all the new options as invalid, it will show "No options" message:
<CreatableSelect isValidNewOption={() => false}/>
You may want to show "Create" option to user but if element exists or any other reason the "Create" option should be hidden.
For exp:
options=[
{label:"A - 1",value:"A"},
{label:"B - 1",value:"B"}
{label:"C - 1",value:"C"}
{label:"D - 1",value:"D"}
]
In my case, user can only Create A, B, C, D but i have formatted their input to make the label look as "A - 1" , "B - 1" and so on. Now if user again enters A, or B or C or D, it will not match with "A - 1", or "B - 1" or "C - 1" or "D - 1" respectively, in this case i want to hide "Create" option because I am already accepting that value but with different format.
So my logic should go as
<CreatableSelect
name="options"
options={options}
placeholder="Select or Create"
isSearchable
onChange={(option) => appendLabel(option.value)}
value={null}
isValidNewOption={(inputValue)=>
(options.filter((lab)=>lab?.label?.split(" — ")
[0].trim().toLowerCase()===inputValue.toLowerCase()).length>0?false:`Create ${inputValue}`}
/>
I am trying to send the contents of the text box to be displayed in the spam when I click on the button
class Hello extends Component {
state = {
texto: ""
}
changeText = () =>{
this.setState({texto: this.state.texto})
}
render() {
return (
<div>
<input type = "text" defaultValue = {this.state.texto}></input>
<p>{this.state.texto}</p>
<button onClick = {this.changeText}>Click</button>
</div>
);
}
}
export default Hello;
function App() {
return (
<div>
<Hello />
</div>
);
}
The idea is that when you click on the button it shows the ones in the text box inside the span.
I am new to react and this clarifies some doubts about the concepts.
thanks guys.
Carlos!
First of all, you must update your input so your code can work properly. In your input, do this instead:
<input type="text" value={this.state.texto} onChange={() => this.onChangeHandler(e.target.value)}></input>
Then, inside your class, you create an onChangeHandler to deal with the data from input first:
onChangeHandler = (e) => {
this.setState({
texto: e
});
}
Ok, we're almost there, now you must create another state item, so you can use it for your final input:
state = {
texto: "",
textoFinished: ""
}
Then, correct your onChange envent:
changeText = () =>{
this.setState({textoFinished: this.state.texto})
}
Now, to access the new value, just go to this.state.textoFinished. Happy coding!
You need to update the state using target value after clicking on it.
Something Like this :
state = {
texto: ""
}
onChangeHandler = (e) => {
const value = e.target.value;
this.setState({texto: value});
}
OR
onChangeHandler = (e) => {
const texto = e.target.value;
this.setState({texto});
}
Newbie React question here on show hide functionality.
I have a state of 'show' that I set to false:
this.state = {
show: false,
};
Then I use the following function to toggle
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
And my display is
{this.state.show && <xxxxxx> }
This all works fine. However I want to apply the function it to multiple cases (similar to accordion, without the closing of other children. So I change my constructor to
this.state = {
show: [false,false,false,false,false,false]
};
and this to recognise there are 6 different 'shows'.
{this.state.show[0] && <xxxxxx> }
{this.state.show[1] && <xxxxxx> } etc
But where I get stuck is how to account for them in my toggleDiv function. How do I insert the square bracket reference to the index of show (if this is my problem)?
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
Thanks for looking.
First of all I'd suggest you not to rely on current state in setState function, but to use the callback option to be 100% sure that you are addressing to the newest state:
this.setState((prevState) => ({ show: !prevState.show }));
How to deal with multiple elements?
You'll have to pass the index of currently clicked element.
{yourElements.map((elem, i) => <YourElem onClick={this.toggleDiv(i)} />)}
and then inside your toggleDiv function:
toggleDiv = (i) => () => {
this.setState((prevState) => {
const r = [...prevState.show]; // create a copy to avoid state mutation
r[i] = !prevState.show[i];
return {
show: r,
}
}
}
Use an array instead of a single value. In your toggle div function make a copy of the state array make necessary changes and push the entire array back up to state at the end.
This is some simplified code showing the workflow I described above
export default class myClass extends React.Component{
constructor(props){
super(props);
this.state = { show: new Array(2).fill(false) };
}
//you need a index or id to use this method
toggleDiv = (index) => {
var clone = Object.assign( {}, this.state.show ); //ES6 Clones Object
switch(clone[index]){
case false:
clone[index] = true
break;
case true:
clone[index] = false
break;
}
this.setState({ show: clone });
}
render(){
return(
<div>
{ this.state.show[0] && <div> First Div </div> }
{ this.state.show[1] && <div> Second Div </div> }
{ this.state.show[2] && <div> Third Div </div> }
</div>
)
}
}
I have a form with a single textarea. When text is entered into this textarea a new textarea should be displayed under the current one. If this new textarea has text entered then again another new one shows underneath (and on and on...).
In order to prevent a new textarea being added every time text is entered (for example if there are 3 textareas and the user focuses and changes the text in the first) I am storing the activeBulletPointId in my state, and when text is entered in it I am checking to see if it is the last bullet point in the array.
addNewBulletToEnd = () => {
let lastBulletId = this.state.data.slice(-1);
lastBulletId = lastBulletId[0].id;
if (this.state.activeBulletPointId === lastBulletId) {
const newBulletPoint = { id: this.generateId(), title: 'Click to add' };
this.setState({ data: this.state.data.concat(newBulletPoint) });
}
}
The issue I have is that when rendering my list I am unsure how to pass the id to the onFocus function.
handleFocus = (e) => {
console.log(e); //Can I get the ID here?
if (this.state.activeBulletPointId !== selectedBulletPointId) {
this.setState({ activeBulletPointId: selectedBulletPointId });
}
}
render() {
const bulletList = this.state.data.map((bulletPoint) => {
const reduxFormName = `${this.props.placeholder}-${bulletPoint.id}`;
return (
<div key={bulletPoint.id} className="bullet-point-input">
<SelectInputType
placeholder={reduxFormName}
type="textarea"
onChange={this.updateText}
onFocus={this.handleFocus}
handleKeyPress={this.handleKeyPress(reduxFormName)}
handleKeyDown={this.handleKeyDown}
noLabel
/>
</div>
);
});
return (
<div className="bullet-point-list">
{bulletList}
</div>
);
}
The <SelectInputType> component is what renders my redux-form <Field> component.
You could create a handler for each field. So you would avoid keeping data in DOM (as attributes) and keep it in handler's scope.
Unless you have hundreds of fields this wont hit overall performance.
setActiveBullet = activeBulletPointId => {
if (this.state.activeBulletPointId !== activeBulletPointId ) {
this.setState({ activeBulletPointId });
}
}
render() {
const bulletList = this.state.data.map((bulletPoint) => {
const reduxFormName = `${this.props.placeholder}-${bulletPoint.id}`;
return (
<div key={bulletPoint.id} className="bullet-point-input">
<SelectInputType
placeholder={reduxFormName}
type="textarea"
onChange={this.updateText}
onFocus={() => this.setActiveBullet(bulletPoint.id)}
handleKeyPress={this.handleKeyPress(reduxFormName)}
handleKeyDown={this.handleKeyDown}
noLabel
/>
</div>
);
});
return (
<div className="bullet-point-list">
{bulletList}
</div>
);
}