I'm trying to find a way to make an editable number with decimals.
Requirements:
I want the value readable by other components. For this reason I store it in a parent state.
The value may be updated by a fetched value. Currently this happens for multiple variables in the parent component.
It doesn't matter if the actual value has more places as long as the input only shows x places.
I'm running into a problem when converting to the fixed value - specifically on Chrome which happens to be the browser of choice. I wrote up a codepen:
https://codepen.io/j1dopeman/pen/wQJNzQ
Only C is using the fixed value. It's stored in the parent state as 'places'. When trying to edit C it immediately converts it to the fixed value which moves the cursor and ruins the input. Backspace also doesn't work as expected. I've tried debouncing the change which didn't work - react won't show the change in the meantime and the second number will get messed up when it eventually updates. I've tried using local state but that interferes with an outside fetch propagating the values down and I think there were other problems too. I just want to enforce the decimal places but not immediately. Someone should be able to type 1.25 or backspace and type a new number with it doing the conversion like a second later.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: {
a: { val: 0 },
b: { val: 0 },
c: { val: 1.5, places: 2 },
d: { val: 0 },
e: { val: 0 },
f: { val: 0 }
}
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(value) {
const update = newVals => {
return state => {
let nv = {};
for (let [key, val] of Object.entries(newVals)) {
nv = { ...nv, [key]: Object.assign(state.inputs[key], val) };
}
const ni = Object.assign(state.inputs, nv);
return { inputs: ni };
};
};
//-----
this.setState(update(value));
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>Calc</h1>
</header>
<InputArea
inputs={this.state.inputs}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
class InputArea extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(value) {
this.props.onInputChange(value);
}
render() {
const inputList = [];
for (let [key, value] of Object.entries(this.props.inputs)) {
inputList.push(
<Variable
key={key}
name={key}
value={value}
onInputChange={this.handleInputChange}
/>
);
}
return (
<div className="input">
<h1>Input</h1>
<div className="input-area">{inputList}</div>
</div>
);
}
}
class Variable extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
let v = this.props.value;
v.val = Number(e.target.value);
this.props.onInputChange({ [this.props.name]: v });
}
render() {
const label = this.props.name;
let val = this.props.value.val;
if (this.props.value.places !== undefined)
val = val.toFixed(this.props.value.places);
return (
<div className="flex-row">
<label>{label}</label>
<input
className="variable-input"
type="number"
name={label}
value={val}
step="any"
onChange={this.handleInputChange}
/>
</div>
);
}
}
So wrapping with parseFloat:
val = parseFloat(val.toFixed(this.props.value.places));
seems to not completely botch the input as the person is typing and backspace mostly works. So that's what I'm using for now. I would still like to know if there's a way to delay formatting an input.
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'm using a bootstrap form slider from 1-5 to describe the condition of something from good - bad. On the slider change, I want it to envoke two functions; one to change the number in the state, and then one to identify the correct text to go with the number and display it as the user slides the slider. I've tried a few different variations of this, but nothing seems to work. Would also welcome other ways of forming the functions or state all together. Thanks in advance.
class ConditionSlider extends Component {
constructor (props) {
super(props);
this.state = {
condition: 1
};
};
render() {
const slideCalls = (e) => {
slideChangeState(e);
conditionText(e)
};
const slideChangeState = (e) => {
this.setState({
condition: e
})
}
let spanText;
const conditionText = (e) => {
if(e === '1' ) {
spanText = <div>Very Poor</div>
} else if (e === '2') {
spanText = <div>Poor</div>
} else if (e === '3') {
spanText = <div>Okay</div>
} else if (e === '4') {
spanText = <div>Good</div>
} else if (e === '5') {
spanText = <div>Excellent</div>
}
}
console.log(this.state.condition)
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{this.spanText}</p>
<p>{spanText}</p>
</div>
)}
};
You need to modify several things first to make it work. First, the render function should not contain those two functions. Then, you could have an object that contains the numbers as keys (1 to 5) and the string that correlates with that number (Poor, excelent, etc.) so you don't fallback to a large if...else statement.
So leaving it as this:
const RANGES = {
1: "Very Poor",
2: "Poor",
3: "Okay",
4: "Good",
5: "Excellent",
}
class ConditionSlider extends Component {
constructor (props) {
super(props);
this.state = {
condition: 1
};
};
slideCalls(e) {
slideChangeState(e);
conditionText(e)
};
slideChangeState(e) {
this.setState({
condition: e
})
}
render() {
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{RANGES[this.state.condition]}</p> // removed <div> here since you cant have a div inside a <p> element. I mean, you could but it would display an error in the console
<p>{spanText}</p>
</div>
)}
};
I'm coding this blind, but this is how I'd handle this situation. If you're using state to manage the value of the slider then you ,ight as well use state to hold the text value too.
I've moved the functions outside of the render method, and as slideChangeState requires access to this I've bound it in the constructor.
I hope this helps - never used this control before
class ConditionSlider extends Component {
constructor(props) {
super(props);
this.state = {
condition: 1,
spanText: "Very Poor"
};
this.slideChangeState = this.slideChangeState.bind(this);
};
slideChangeState(value) {
var spanText = conditionText(value);
this.setState({
condition: value,
spanText: spanText
})
};
conditionText(value) {
switch (value) {
case 1:
return "Very Poor";
case 2:
return "Poor";
case 3:
return "Okay";
case 4:
return "Good";
case 5:
return "Excellent"
}
}
render() {
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{this.state.spanText}</p>
</div>
)
}
};
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.
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 currently have something like this:
const socket = require('socket.io-client')('https://example.com');
(....)
// Listen to the channel's messages
socket.on('m', message => {
// this is a Redux action that updates the state
this.props.updateTrades(message);
});
The reducer looks like this:
case actions.UPDATE_TRADES:
return {
...state,
trades: [
...state.trades,
action.trade
]
};
I've tried not using redux and just to the following:
socket.on('m', message => {
this.setState(state => {
if (state.trades.length > 99) {
state.trades.splice(0, 1);
}
return {
trades: [
...state.trades,
message
]
});
});
I don't need to keep increasing my trades array. I'm happy just to keep around 100 items or so...
Socket is sending around 15 messages / second.
My problem is: I can't seem to render the messages in real-time! It just freezes. I guess the stream is just too fast? Any suggestions?
Thanks in advance!
The thing is to do the minimum possible and when the trades change only draw what has change and not all of the elements of the array.A technique that I use is to keep a cache map of already drawn obj, so in the render method I only render the new incoming elements.
Take a look at https://codesandbox.io/s/wq2vq09pr7
class RealTimeList extends React.Component {
constructor(props) {
super(props);
this.cache = [];
}
renderRow(message, key) {
return <div key={key}>Mesage:{key}</div>;
}
renderMessages = () => {
//let newMessages=this,props.newMessage
let newElement = this.renderRow(this.props.message, this.cache.length);
this.cache.push(newElement);
return [...this.cache];
};
render() {
return (
<div>
<div> Smart List</div>
<div className="listcontainer">{this.renderMessages()}</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "hi" };
}
start = () => {
if (this.interval) return;
this.interval = setInterval(this.generateMessage, 200);
};
stop = () => {
clearTimeout(this.interval);
this.interval = null;
};
generateMessage = () => {
var d = new Date();
var n = d.getMilliseconds();
this.setState({ title: n });
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.start}> Start</button>
<button onClick={this.stop}> Stop</button>
<RealTimeList message={this.state.message} />
</div>
);
}
}
The class RealTime List have a cache of elements.Let me know if this helps.
It's probably not a good idea to try to render all of the changes. I think you should try rendering them in batches so you only update once every few seconds, that should help.