Hello Im a beginner in React have a button in a react component and I want to pass 2 options to its onClick method, something like:
handleClick = clickType => {
const {currentStep} = this.state
let newStep = currentStep
clickType === 'next' ? newStep++ : newStep--
if (newStep > 0 && newStep <= 6) {
this.setState({
currentStep: newStep
});
}
}
handleChange = input => e => {
this.setState({ [input]: e.target.value });
};
continue = e => {
e.preventDefault();
this.props.nextStep();
};
back = e => {
e.preventDefault();
this.props.prevStep();
};
<button onClick={() => this.handleClick(), this.back} className='previous'>قبلی</button>
<button form='my-form' type='submit' onClick={() => this.handleClick('next'), this.continue} className='next'>ادامه</button>
How can I achieve this correctly?
The arrow function inside the onClick can have execute more than one function.
It is still a function, and you can put code in it ;)
But maybe you can improve your current code :
handleClick = clickType => {
const {currentStep} = this.state
let newStep = currentStep
clickType === 'next' ? newStep++ : newStep--
if (newStep > 0 && newStep <= 6) {
this.setState({
currentStep: newStep
});
}
}
handleChange = input => e => {
this.setState({ [input]: e.target.value });
};
continue = e => {
e.preventDefault();
this.props.nextStep();
};
back = e => {
e.preventDefault();
this.props.prevStep();
};
<button onClick={(e) => { this.handleClick(); this.back(e); }} className='previous'>قبلی</button>
<button form='my-form' type='submit' onClick={() => { this.handleClick('next'); this.continue(e); }} className='next'>ادامه</button>
By this version:
handleNext = (e) => {
const { currentStep } = this.state;
if (currentStep >= 0 && currentStep <= 5) {
this.setState({
currentStep: currentStep++
});
}
this.props.nextStep();
}
handlePrevious = (e) => {
const { currentStep } = this.state;
if (currentStep > 0 && currentStep <= 5) {
this.setState({
currentStep: currentStep--
});
}
this.props.prevStep();
}
<button onClick={this.handlePrevious} className='previous'>قبلی</button>
<button form='my-form' type='submit' onClick={this.handleNext} className='next'>ادامه</button>
You need to setup your handling function differently.
Rather have something like this:
handleBack = e => {
e.preventDefault()
if (this.state.currentStep > 1) {
this.setState((prevState) => ({
currentStep: prevState.currentStep - 1
}));
}
this.props.prevStep()
}
handleNext = e => {
e.preventDefault()
if (this.state.currentStep < 6) {
this.setState((prevState) => ({
currentStep: prevState.currentStep + 1
}));
}
this.props.nextStep()
}
<button onClick={this.handleBack} ... />
<button onClick={this.handleNext} ... />
This method is a lot cleaner and it easier to read because each function deals with its own click.
Now you can easily see exactly what is happening when you click back, and exactly what is happening when you click next.
You can use something like this
/**
I copied this function from code, please make sure that its working.
*/
handleChange = input => e => {
this.setState({ [input]: e.target.value });
};
updateStep = step => {
if (step > 0 && step <= 6)
this.setState({
currentStep: newStep
});
}
/**
Try to avoid the keywords like continue, break, for, while etc as
variable or function names.
*/
handleContinue = e => {
e.preventDefault();
this.handleClick(this.state.currentStep+1);
this.props.nextStep();
};
handleBack = e => {
e.preventDefault();
this.handleClick(this.state.currentStep-1);
this.props.prevStep();
};
<button onClick={this.handleBack} className='previous'>قبلی</button>
<button form='my-form' type='submit' onClick={this.handleContinue} className='next'>ادامه</button>
Related
I created a stopwatch using react. My stopwatch starts from 0 and stops at the press of the space button with componenDidMount and componentWillMount. My issue is, I can't seem to figure out how to create some sort of list with the numbers the stopwatch returns. I've created:
times = () => {
this.setState(previousState => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
and then in render() to print it.
<h1>{this.times}</h1>
What I'm trying to do is to create some sort of array that'll keep track of milliSecondsElapsed in my handleStart and handleStop method.
Here's what I have.
import React, {Component} from "react";
import Layout from '../components/MyLayout.js';
export default class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
this.updateState = this.updateState.bind(this);
this.textInput = React.createRef();
}
componentDidMount() {
window.addEventListener("keypress", this.keyPress);
}
componentWillUnmount() {
window.removeEventListener("keypress", this.keyPress);
}
textInput = () => {
clearInterval(this.timer);
};
updateState(e) {
this.setState({})
this.setState({ milliSecondsElapsed: e.target.milliSecondsElapsed });
}
keyPress = (e) => {
if (e.keyCode === 32) {
// some logic to assess stop/start of timer
if (this.state.milliSecondsElapsed === 0) {
this.startBtn.click();
} else if (this.state.timerInProgress === false) {
this.startBtn.click();
} else {
this.stopBtn.click();
}
}
};
handleStart = () => {
if (this.state.timerInProgress === true) return;
this.setState({
milliSecondsElapsed: 0
});
this.timer = setInterval(() => {
this.setState(
{
milliSecondsElapsed: this.state.milliSecondsElapsed + 1,
timerInProgress: true
},
() => {
this.stopBtn.focus();
}
);
}, 10);
};
handleStop = () => {
this.setState(
{
timerInProgress: false
},
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
times = () => {
this.setState(previousState => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
render() {
return (
<Layout>
<div className="index" align='center'>
<input
value={this.state.milliSecondsElapsed/100}
onChange={this.updateState}
ref={this.textInput}
readOnly={true}
/>
<button onClick={this.handleStart} ref={(ref) => (this.startBtn = ref)}>
START
</button>
<button onClick={this.handleStop} ref={(ref) => (this.stopBtn = ref)}>
STOP
</button>
<h1>{this.state.milliSecondsElapsed/100}</h1>
</div>
</Layout>
);
}
}
Issue
this.times is a function that only updates state, it doesn't return any renderable JSX.
times = () => {
this.setState((previousState) => ({
myArray: [...previousState.myArray, this.state.milliSecondsElapsed]
}));
};
Solution
Create a myArray state.
this.state = {
myArray: [], // <-- add initial empty array
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
Move the state update logic from this.times to this.handleStop.
handleStop = () => {
this.setState(
(previousState) => ({
timerInProgress: false,
myArray: [
...previousState.myArray, // <-- shallow copy existing data
this.state.milliSecondsElapsed / 100 // <-- add new time
]
}),
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
Render the array of elapsed times as a comma separated list.
<div>{this.state.myArray.join(", ")}</div>
Full code
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
myArray: [],
milliSecondsElapsed: 0,
timerInProgress: false // state to detect whether timer has started
};
this.updateState = this.updateState.bind(this);
this.textInput = React.createRef();
}
componentDidMount() {
window.addEventListener("keypress", this.keyPress);
}
componentWillUnmount() {
window.removeEventListener("keypress", this.keyPress);
}
textInput = () => {
clearInterval(this.timer);
};
updateState(e) {
this.setState({ milliSecondsElapsed: e.target.milliSecondsElapsed });
}
keyPress = (e) => {
if (e.keyCode === 32) {
// some logic to assess stop/start of timer
if (this.state.milliSecondsElapsed === 0) {
this.startBtn.click();
} else if (this.state.timerInProgress === false) {
this.startBtn.click();
} else {
this.stopBtn.click();
}
}
};
handleStart = () => {
if (this.state.timerInProgress === true) return;
this.setState({
milliSecondsElapsed: 0
});
this.timer = setInterval(() => {
this.setState(
{
milliSecondsElapsed: this.state.milliSecondsElapsed + 1,
timerInProgress: true
},
() => {
this.stopBtn.focus();
}
);
}, 10);
};
handleStop = () => {
this.setState(
(previousState) => ({
timerInProgress: false,
myArray: [
...previousState.myArray,
this.state.milliSecondsElapsed / 100
]
}),
() => {
clearInterval(this.timer);
this.startBtn.focus();
}
);
};
render() {
return (
<div>
<div className="index" align="center">
<input
value={this.state.milliSecondsElapsed / 100}
onChange={this.updateState}
ref={this.textInput}
readOnly={true}
/>
<button
onClick={this.handleStart}
ref={(ref) => (this.startBtn = ref)}
>
START
</button>
<button onClick={this.handleStop} ref={(ref) => (this.stopBtn = ref)}>
STOP
</button>
<h1>{this.state.milliSecondsElapsed / 100}</h1>
</div>
<div>{this.state.myArray.join(", ")}</div>
</div>
);
}
}
I am new to react, I am trying to write a react component, component has several features.
user can input a random number, then number will be displayed in the
page too.
implement a button with text value 'start', once click the button,
the number value displayed will reduce one every 1second and the
text value will become 'stop'.
continue click button, minus one will stop and text value of button
will become back to 'start'.
when number subtracted down to 0 will automatically stop itself.
I have implemented first three features. but I am not sure how do I start the last one. should I set another clearInteval? based on if statement when timer counts down 0?
code is here:
var myTimer;
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [{ id: 1, number: "" }],
type: false
};
this.handleClick = this.handleClick.bind(this);
}
changeNumber = (e, target) => {
this.setState({
details: this.state.details.map(detail => {
if (detail.id === target.id) {
detail.number = e.target.value;
}
return detail;
})
});
};
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
}
if (this.state.type === true) {
clearInterval(myTimer);
}
};
render() {
return (
<div>
{this.state.details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => this.changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => this.handleClick()}
value={this.state.type ? "stop" : "start"}
/>
</div>
);
})}
</div>
);
}
}
export default App;
just add
if (detail.number === 0) {
clearInterval(myTimer);
}
in
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
if (detail.number === 0) {
clearInterval(myTimer);
}
}
return detail;
})
}),
1000
);
}
if (this.state.type === true) {
clearInterval(myTimer);
}
};
Here You have this solution on Hooks :)
const Test2 = () => {
const [on, setOn] = useState(false)
const initialDetails = [{ id: 1, number: "" }]
const [details, setDetails] = useState(initialDetails)
const changeNumber = (e, target) => {
setDetails({ details: details.map(detail => { if (detail.id === target.id) { detail.number = e.target.value; } return detail; }) });
if (this.state.details.number === 0) { setOn(false) }
};
const handleClick = () => {
if (on === false) {myTimer = setInterval(() =>
setDetails({details: details.map(detail => {if (detail.id) {detail.number = parseInt(detail.number) - 1; if (detail.number === 0) {clearInterval(myTimer);} }
return detail;})}),1000);}
if (on === true) { clearInterval(myTimer); }
};
return (
<div>
{details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => handleClick()}
value={on ? "stop" : "start"}
/>
</div>
);
})}
</div>
)
}
I have a toggle menu and I'm trying detect the click events outside of a menu to be able to close the menu, I can close the menu when user clicks outside of the menu, however to open the menu again you would have to click on it twice, does anyone know what I have to do to fix that, (the menu should open with one click)
const RightMenu = ({ t, history }) => {
let [menuOpen, setMenuOpen] = useState(false);
const menuDiv = useRef({});
const toggleMenu = useRef();
useEffect(() => {
window.addEventListener("click", () => {
if ((menuDiv.current.style.display = "block")) {
menuDiv.current.style.display = "none";
}
});
return () => {
window.removeEventListener("click", () => {});
};
}, []);
const handleClick = e => {
e.stopPropagation();
if (menuOpen === false) {
menuDiv.current.style.display = "block";
setMenuOpen(true);
}
if (menuOpen === true) {
menuDiv.current.style.display = "none";
setMenuOpen(false);
}
};
return (
<div>
<div
id="menu"
ref={menuDiv}
style={{
display: "none"
}}
>Menu items</div>
<div
className="text-center"
ref={toggleMenu}
onClick={e => handleClick(e)}
> Menu Button</div>
)
}
const RightMenu = ({ t, history }) => {
let [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
window.addEventListener("click", () => {
setMenuOpen(prevState => {
return !prevState
})
});
return () => {
window.removeEventListener("click", () => {});
};
}, []);
const handleClick = () => {
e.stopPropagation();
setMenuOpen(!menuOpen);
};
return (
<div>
{menuOpen && (<div
id="menu"
>Menu items</div>)}
<div
className="text-center"
onClick={handleClick}
> Menu Button</div>
)
You do not need refs to achieve this, u can conditionally render the menu based on the menuOpen state like in the example provided.
You are not actually removing the event listener from window when your component unmounts. The second argument to the removeEventListener should be a reference to the same function you added with addRemoveListener. E.g.
useEffect(() => {
const closeMenu = () => {
if ((menuDiv.current.style.display = "block")) {
menuDiv.current.style.display = "none";
}
};
window.addEventListener("click", closeMenu);
return () => {
window.removeEventListener("click", closeMenu);
};
}, []);
#Watch('openDropdown')
openHandler(newValue) {
newValue ? this.setOpen() : this.setClose();
}
componentWillLoad() {
document.addEventListener('click', this.handleClick, false)
}
componentDidUpdate() {
//
if (this.dropdownNode != null && this.collapsableIconNode != null) {
this.dropdownNode.style.top = this.collapsableIconNode.offsetTop + 20 + 'px'
this.dropdownNode.style.left = this.collapsableIconNode.offsetLeft - 11 + 'px'
}
}
componentDidUnload() {
document.removeEventListener('click', this.handleClick, true)
}
handleClick = (e) => {
if (this.collapsableIconNode.contains(e.target)) {
this.openDropdown = true;
}
else {
this.handleClickOutside()
}
}
handleClickOutside() {
this.openDropdown = false;
}
<span ref={collapsableIconNode => this.collapsableIconNode = collapsableIconNode as HTMLSpanElement} id="collapseIcon" class="collapsable-icon" onClick={() => this.setOpen()}>
This is Written in StencilJS, Logic is same,It is similar to React!!
I was dealing with the same problem recently. I ended up using this lib - https://github.com/Andarist/use-onclickoutside
Worked perfectly for me. Minimal effort. It covers all the edge cases.
Maybe you should give it a try.
I have almost finished building a simple calculator, using React.
I just have a trouble with multiple decimals. What I try to do is writing a condition but it doesn't work. Could you help me, please?
Here is a part of my code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '0'
};
}
addToInput = e => {
const value = e.target.value;
const oldValue = this.state.input;
if (this.state.input != '0') {
this.setState({ input: (this.state.input + value) });
} else if (value == '.' && oldValue.includes('.')) {
console.log('Mulitple decimals');
} else {
this.setState({ input: value });
}
};
render() {
return (<Button addToInput={ this.addToInput } />);
}
}
class Button extends React.Component {
render() {
return (
<div className="row">
<button
value="."
id="decimal"
onClick={ this.props.addToInput }
>.
</button>
<button
value="0"
id="zero"
onClick={ this.props.addToInput }
>0
</button>
<button
value="-"
id="subtract"
onClick={ this.props.addToInput }
>-
</button>
</div>
);
}
}
Thank you in advance!
Change you addToInput like this:
addToInput = e => {
const value = e.target.value;
const oldValue = this.state.input;
if (value === '.' && oldValue.includes('.')) {
console.log('Mulitple decimals');
return;
}
if (this.state.input !== '0') {
this.setState({ input: (this.state.input + value) });
} else {
this.setState({ input: value });
}
};
Why you have had a problem:
addToInput = e => {
const value = e.target.value;
const oldValue = this.state.input;
if (this.state.input !== '0') {
// after first addToInput you will end up here ALWAYS
this.setState({ input: (this.state.input + value) });
} else if (value === '.' && oldValue.includes('.')) {
// You will never be here because this.state.input !== '0' is always true after first addToInput
console.log('Mulitple decimals');
} else {
// you will end up here only when you lunch your addToInput first time
this.setState({ input: value });
}
};
You could look at the value coming in, check if it's a . and check if the input already has one. If it does, do nothing, otherwise add the value to the end of the input:
addToInput = e => {
const { value } = e.target;
const { input } = this.state;
if (value === "." && input.includes(".")) {
return;
}
this.setState({ input: `${input}${value}` });
};
Well regular expression will help you.
const regex = /^[1-9]\d*(\.\d+)?$/;
Then you can check your value:
regex.test('222') // true
regex.test('222.') // false
regex.test('222.0') // true
regex.test('222.0.1') // false
regex.test('222.01234') // true
regex.test('abc') // false
I have this button in react
{editing && (
<Button extraClasses="m1" onClick={this.handleEditing} type="submit">
Save
</Button>
)}
But the submit doesn't work, if I delete the onClick, the submit works. How can I make both, the onClick and the submit to work?
This is the onSubmit event:
handleSubmit(e) {
e.preventDefault();
const params = this.state.params || this.props.selected.params;
const { exportTypes } = this.props;
const {
startDate: startDateMoment,
endDate: endDateMoment,
companyId,
exportTypeId,
id,
} = this.state;
const type = exportTypes.find(o => o.id === Number(exportTypeId));
let enrichedParams = [];
if (type.params.length > 0) {
enrichedParams = params.reduce((acc, { paramName, paramValue }) => {
const { id: exportParameterId } = type.params.find(p => p.name === paramName);
return [...acc, { exportParameterId, paramName, paramValue }];
}, []);
}
const startDate = startDateMoment.format();
const endDate = endDateMoment.format();
const record = { companyId, exportTypeId, startDate, endDate, id, params: enrichedParams };
const filteredQuery = Object.keys(record).reduce(
(acc, k) => (record[k] ? { ...acc, [k]: record[k] } : acc),
{},
);
if (!Object.keys(filteredQuery).length) return;
this.props.updateExport(filteredQuery);
}
You could remove the onClick event handler from your Button and invoke the handleEditing method inside your handleSubmit method instead.
Example
class App extends React.Component {
handleEditing = () => {
// ...
};
handleSubmit = (e) => {
// ...
this.handleEditing();
};
render() {
return (
<div>
{/* ... */}
{editing && (
<Button extraClasses="m1" type="submit">
Save
</Button>
)}
{/* ... */}
</div>
);
}
}