Remains state on React - javascript

I'm making an application now with react.
Set the calculation function, and sum: 0 specified by state remains at the beginning.
This is the actual operation.
https://www.useloom.com/share/43bd30dc0f0741f7b09c63a3728d2ba9
import React, { Fragment, Component } from "react";
class Account extends Component {
state = { sum: 0 };
input = event => {
this.setState({ input: event.target.value });
console.log(this.state.input);
};
Plus = () => {
this.setState({ sum: this.state.sum + this.state.input });
};
Main = () => {
this.setState({ sum: this.state.sum - this.state.input });
};
render() {
return (
<Fragment>
<h2>口座</h2>
<h3>
現在:
{this.state.sum}
</h3>
<input onChange={this.input} />
<br />
<button onClick={this.Plus}>収入</button>
<button onClick={this.Main}>支出</button>
</Fragment>
);
}
}
export default Account;
Please let me know the way to erase the leading zeros and why sum: 0 is left.
Thank you.

First, you are doing a string concatenation rather than a sum.
You need to get a number from your input, for example through + operator:
this.setState({ input: +(event.target.value) });
Then, when you want to set the state based on the previous state, you should use the setState method with a callback as argument, which provides you the previous state, like:
this.setState(prevState => ({ sum: prevState.sum + prevState.input }));
Then when you init your state, you should specify all the state:
state = { sum: 0, input: 0 };
So your final code should look something like:
import React, { Fragment, Component } from "react";
class Account extends Component {
state = { sum: 0, input: 0 };
input = event => {
this.setState({ input: +(event.target.value) });
console.log(this.state.input);
};
Plus = () => {
this.setState(prevState => ({ sum: prevState.sum + prevState.input }));
};
Main = () => {
this.setState(prevState => ({ sum: prevState.sum - prevState.input }));
};
render() {
return (
<Fragment>
<h2>口座</h2>
<h3>
現在:
{this.state.sum}
</h3>
<input value={this.state.input} onChange={this.input} />
<br />
<button onClick={this.Plus}>収入</button>
<button onClick={this.Main}>支出</button>
</Fragment>
);
}
}
export default Account;

Two things: your input is becoming uncontrolled, and you have to cast the value from string to number.
The first is a subtle issue that will be more helpful for more advanced app you'll made. You have to assign a value prop to your input, this way:
<input value={ this.state.input } onChange={this.input} />
Remeber also to assign an initial value to state.input, something like an empty string is fine.
The main problem you have is the casting of the input value to a string, you can do it with:
this.setState({ input: Number(event.target.value) })
One more thing, when you update state using value from the state itself, use the method notation instead the object one:
this.setState(prevState => ({ sum: prevState.sum + prevState.input }))

Related

Optimizing performance for arbitrarily large number of inputs in react

Specifically with Material UI, I'm trying to render a large number of inputs (> 100) and manage a global state effectively. I run into serious performance issues like input lag at around 50 inputs but the lag is still noticeable at around 10 inputs. I've narrowed it down to the Material UI <TextField /> component because if I change that to regular input element, there is virtually no lag which is to be expected since there's a lot more happening in a MUI component.
What I don't know however, is if changing how I manage state might help performance. I've tried 2 state management approaches: traditional react with useState hook and Recoil.
In addition to the 2 approaches above, I also tried a combination of recoil and traditional react by using useState and useSetRecoilState in filter components so that all filters wouldn't re-render on change to recoil state since they are only writing not reading but still no luck.
EDIT
Turns out I forgot to test putting values in more than 1 input... When you enter a value in another input, the previously entered input will get wiped. I'm thinking it's because in my handleChange function, I replace an item in filterList but filterList is now an old outdated reference so it does not have the previously set value. Back at square one now, because if I read state in the filter component, then the component will re-render which eliminates the memo performance boost.
EDIT 2
Posted solution below
Looks like I was trying to use recoil when I really didn't need to. Using React.memo along with React.useState and moving the global state edit up to the parent ended up being all I needed.
Filters.js
const filterMaker = (length) => {
let filters = [];
for (let i = 0; i < length; i++) {
if (i % 2 === 0) {
filters.push({ name: `filter-${i}`, type: 'a', active: { value1: '' } });
} else {
filters.push({
name: `filter-${i}`,
type: 'b',
active: { value1: '', value2: '' },
});
}
}
return filters;
};
function replaceItemAtIndex(arr, index, newValue) {
return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
}
export default function Filters() {
const [filterList, setFilterList] = useRecoilState(filterListState);
useEffect(() => {
setFilterList(filterMaker(200));
}, []);
const handleChange = (filter, name, value, index) => {
setFilterList((prevState) => {
return replaceItemAtIndex(prevState, index, {
...filter,
active: {
...filter.active,
[name]: value,
},
});
});
};
return (
<div>
{filterList.map((filter) =>
filter.type === 'a' ? (
<FilterA
key={filter.name}
filter={filter}
filterList={filterList}
handleChange={handleChange}
/>
) : (
<FilterB
key={filter.name}
filter={filter}
filterList={filterList}
handleChange={handleChange}
/>
)
)}
</div>
);
}
FilterA.js
export const FilterA = memo(
({ filter, filterList, handleChange }) => {
const [values, setValues] = useState(filter.active || {});
const index = filterList.findIndex((item) => item.name === filter.name);
const handleLocalChange = ({ target: { name, value } }) => {
setValues((prevState) => {
return { ...prevState, [name]: value };
});
handleChange(filter, name, value, index);
};
return (
<Box mb={1}>
<Typography>{filter.name}</Typography>
<TextField
variant="standard"
name="value1"
label="value1"
value={values.value1 || ''}
onChange={handleLocalChange}
/>
</Box>
);
},
(prevProps, nextProps) => {
if (prevProps.filter.active.value1 === nextProps.filter.active.value1) {
return true;
}
return false;
}
);

React - How can i add new value to an array and display this array in alert window?

I am writing a really simple program but I have got a small problem.
When I write a new omen in input label, I want to add this to the array that is in state and display whole content of the array in alert box.
How can I do it in the easiest way?
class Draw extends React.Component {
state = {
omen: "",
omens: ["first", "second", "third"],
newOmen: "",
};
handleRandomOmen = () => {
const omensArray = this.state.omens;
const randomOmen =
omensArray[Math.floor(Math.random() * omensArray.length)];
this.setState({
omen: randomOmen,
});
};
handleChange = (e) => {
this.setState({
newOmen: e.target.value,
});
};
handleAddOmen = () => {
const test = this.state.omens.push(this.state.newOmen);
console.log(test);
};
render() {
return (
<div className="app">
<button onClick={this.handleRandomOmen}>Show omen</button>
<br />
<input
type="text"
value={this.state.newOmen}
onChange={this.handleChange}
/>
<button onClick={this.handleAddOmen}>Add omen</button>
<h1>{this.state.omen}</h1>
</div>
);
}
}
ReactDOM.render(<Draw />, document.getElementById("root"));
you always need to update a state with setState,
adding a new array value with push will be overriden by the next react cycle.
You can use your the spread syntax to create a new array with the old values + your new one:
handleAddOmen = () => {
this.setState({
omens: [
...this.state.omens,
this.state.newOmen
]
});
};

ReactJS Text Input

I tried to make a small program that has an input field where you can insert text. Then when you click save, it saves the text and shows you below. My question is that, initially the text was not showing up after I clicked 'save', but only when I made another change to the input field after clicking 'save'.
After adding this.setState({inputText: ' '}) in saveValue, it started to work but I'm not so sure why.
import React, {Component} from 'react'
import './App.css';
import picture from './picture.jpg'
class App extends Component {
state = {
inputText: '',
savedValues: [],
}
textStorage = (event) => {
this.setState({
inputText: event.target.value
})
}
saveValue = (inputText) => {
this.state.savedValues.push(this.state.inputText)
this.setState({inputText: ' '})
}
render() {
return (
<div className = "App">
<h1>Hello World</h1>
<input
type = "text"
value = {this.state.inputText}
onChange = {(event) => this.textStorage(event)}
/>
<p>Here's your text: {this.state.inputText}</p>
<button onClick = {this.saveValue}>Save</button>
<p>{this.state.savedValues.join('')}</p>
<div>
<img className = "Picture" src={picture} alt="Picture"/>
</div>
</div>
)
}
}
export default App;
Issue
You are mutating your state object when you push a value into it. You aren't returning a new array state reference so react doesn't rerender until you actually update state by updating the input value and trigger the rerender. Adding the update to clear the input value was the state update that triggered a rerender.
saveValue = (inputText) => {
this.state.savedValues.push(this.state.inputText) // <-- mutation!
this.setState({inputText: ' '})
}
Solution
Use a correct state update. array.prototype.concat concatenates a value to and returns a new array reference.
saveValue = () => {
this.setState(prevState => ({
savedValues: prevState.savedValues.concat(prevState.inputText),
inputText: ' ',
}))
}
Alternatively you can use the Spread syntax to create a new array as well.
saveValue = () => {
this.setState(prevState => ({
savedValues: [...prevState.savedValues, prevState.inputText],
inputText: ' ',
}))
}

React state not changing

I have a react component like this:
const students: [
{id: '', name: '', age: '', attendance: ''}
//... 1000 entries
]
class Students extends React.Component {
constructor (props) {
super(props)
this.state = {
studentID: 1
}
}
createMonthlyChart = () => {
const { studentID } = this.state
let computeScore = students.attendance.map (
item => {
//... get attendance by id
}
)
return chartData
}
handleOnChange = value => {
console.log(value) // student key
this.setState({
studentID: value
})
this.createMonthlyChart() // not working
}
render () {
return (
<div>
// ant design component
<Select
defaultValue={type}
onChange={this.handleOnChange}
>
students.map((student, key) => (
<Option key={student.id}> {student.name} </Option>
))
</Select>
</div>
)
}
}
That is the just idea
I am not sure if I am using setState wrongly but somehow I get unexpected value
For example, the first time I click on a student, I don't get any chart visualization even though the data is there, I have to press it second time to get any chart.
And If I click on student without any attendance, I get empty chart after that for all students. I have to refresh the page to restart
To me it seems like you don't need the studentID state at all, you could directly call this.createMonthlyChart passing the value as a parameter to the function.
But if you need it for some other reason you can invoke that function as a callback to the setState like this:
this.setState({
studentID: value
}, this.createMonthlyChart)
I see a couple of things
The option should have the value
<option key={student.id} value={student.id}> {student.name}</option>
createMonthlyChart, should be called after updating the state (second parameter)
And you should use event.target.value
handleOnChange = event => {
this.setState({
studentID: event.target.value,
}, this.createMonthlyChart);
};
And for the first time, you can use componentDidMount
componentDidMount() {
this.createMonthlyChart();
}
And don't forget to initialize the state with the first student, like this
this.state = {
studentID: students[0].id,
};

Generic function in react with multiple state variables

How to write a generic function with various state variable change based on the dropdown.
for ex:
I have dropdown 1 and dropdown 2. If I change the
dropdown 1, I need to change the few state variables like a, b, c
dropdown 2, I need to change the few state variables like x, y, z
I can do this with 2 functions. But how to write a a generic function for this?
handleChange: function(e) {
//api call to xyz.com/getcompany/<company id> to get the job list here
this.setState({
selectedCompany: e.target.value,
companychange: "company changed. New value: " + e.target.value
})
},
handleChange2: function (e) {
// api call to xyz.com/jobstatus/<job id> to get the job status\(like how many position available for this job\) here
this.setState({
jobchange:"job changed. New value " + e.target.value
})
}
Codepen: https://codepen.io/asdhanapal/pen/WmwJPj?editors=0011
You could use a curried function to simplify the code a bit:
changeToState(fn) {
return e => this.setState(fn(e));
}
render() {
//...
<input onChange={changeToState(e => ({ selectedCompany: e.target.value, /*...*/ }))} />
<input onChange={changeToState(e => ({ jobChange: e.target.value, /*...*/ }))} />
}
If that is still to much boilerplate, you could extract the handleChange event into a functional component:
const StateChanger = ({ action, parent, ...props }) => (
<input onChange={e => parent.setState(action(e))} {...props} />
);
// inside a component's render:
<StateChanger parent={this} action={e => ({ selectedCompany: e.target.value })} style={{ color: red }} />
but as I already mentioned in the comments, that might remove repeated code a bit, but it doesn't improve readability / maintainability whatsoever.
You can use below snippet:
handleChange = event => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
};
Reference to react docs section:
https://reactjs.org/docs/forms.html#handling-multiple-inputs
You can use following:
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]:value
})
}
// in input
<input name="username" onChange={this.handleChange} />
Try this:
handleChange: function(source, e) {
switch(source) {
case 'company':
//Need to do api call to get the job list here
this.setState({
selectedCompany: e.target.value,
companychange: "company changed. New value: " + e.target.value
})
break;
case 'job':
// Need to do api call to get the job status\(like how many position available for this job\) here
this.setState({
jobchange:"job changed. New value " + e.target.value
})
break;
};
},
<select value={this.state.selectedCompany} onChange={this.handleChange.bind(this, 'company')}>
<select value={this.state.selectedCompany} onChange={this.handleChange.bind(this, 'job')}>
As I read your description of the requirements, there are no functional dependencies between dropdown 1 and dropdown 2, so I'd split this up in two separate components.

Categories

Resources