I have a save button that saves the checkbox and the number field, then makes a call to update those numbers. I am adding a 'Cancel' button that I want to be able to use to revert the status to the previous state (prior to saving). what is the best way to do this in React?
This is my code
class Alerts extends Component {
state = {
prevId: null,
checked: this.props.preferences.last_order_alert_enabled,
days: this.props.preferences.last_order_alert
};
static getDerivedStateFromProps(props, state) {
// Store prevId in state so we can compare when props change.
// Clear out previously-loaded data (so we don't render stale stuff).
if (props.dealerID !== state.prevId) {
//update action goes here...
//props.actions.getUsers(props.dealerID);
return {
prevId: props.dealerID
};
}
// No state update necessary
return null;
}
componentDidMount = () => {
console.log('mountDays',this.props.preferences.last_order_alert);
console.log('mountCheck',this.props.preferences.last_order_alert_enabled);
this.setState({ days: this.props.preferences.last_order_alert });
this.setState( {checked: this.props.preferences.last_order_alert_enabled})
};
toggleAlerts = e => {
console.log('lastOrderEnable', this.props.preferences.last_order_alert_enabled);
console.log('lastOrderDaysAlert', this.props.preferences.last_order_alert);
this.props.actions.updateLastOrderAlert(
this.props.preferences.id,
this.props.dealerID,
this.props.isGroup,
this.props.preferences.last_order_alert = this.state.checked,
this.props.preferences.last_order_alert_enabled = this.state.days
);
};
handleCheck = event => {
console.log('called', {checked: Number(event.target.checked) });
this.setState({checked: Number(event.target.checked) })
};
handleChange = e => {
console.log('days', {days: e.target.value});
this.setState({days: e.target.value})
};
render = () => {
return (
<div className="preferenceContainer">
<div className="preferenceRow lg">
<div className="preferenceLabelWrapper">
<div className="preferenceLabel">Enable Alert</div>
<div className="preferenceSubLabel">
Toggles the Last Order Alert Email
</div>
</div>
<div className="preferenceInput">
<input
type="checkbox"
checked={this.state.checked}
onChange={this.handleCheck.bind(this)}
/>
</div>
</div>
<div className="preferenceRow">
<div className="preferenceLabelWrapper">
<div className="preferenceLabel">Days Since Last Order</div>
</div>
<div className="preferenceInput">
<input
type="number"
value={this.state.days}
// onBlur={this.handleAlertDays}
onChange={this.handleChange.bind(this)}
style={{ width: "50px" }}
/>
</div>
</div>
<div className="preferenceRow" style={{ display: "flex" }}>
<button
className={'btn btn-default'}
type="submit"
onClick={this.componentDidMount}
>Cancel
</button>
<button
style={{ marginLeft: "auto" }}
className={'btn btn-primary'}
type="submit"
onClick={this.toggleAlerts}
>Save
</button>
</div>
</div>
);
};
}
Right now is currently saving the changes and rendering them back when I hit the cancel button(prior to hitting save). However, after hitting save and going to another page and then coming back, it does not render with the initial state in componentDidMount.
Never use lifecycle methods in onClick
If I understood correctly, you should use componentDidUpdate(prevProps, prevState) lifecycle method to get previous props and previous state after change them. Every time after redrawing react will give you previous props and previous state in that method.
Sorry, if this is not what you need
Related
I want to push the tip which I'm calculating in #this.calculate onto tip_arr[]. In that I'm not able to access my first item of as it is showing ["",item,item]. Here my first item is getting empty just the string. Here I'm calculating per person tip how much he is paying and after that diplay that.
export default class Calculation extends React.Component {
constructor(props) {
super(props)
this.state = {
amount: '',
tip_per: '',
name: '',
tip: '',
name_arr:[],
tip_arr:[]
}
this.handle = (event) => {
this.setState({ tip_per: event.target.value });
}
this.calculate = () => {
this.setState({ tip: ((this.state.amount) * this.state.tip_per) / 100 })
this.name_change()
this.tip_change()
}
this.name_change=()=>{
let{name_arr,name}=this.state
name_arr.push(name)
}
this.tip_change=()=>{
let{tip_arr,tip}=this.state
tip_arr.push(tip)
}
}
render(){
return(
<>
<div className='Header'>
<header>Tip Calculator<br />Build in React</header>
</div>
<div className='Input'>
<h4>Enter the Bill Amount:-</h4>
<input type='number' className='width' value={this.state.amount}
onChange={(e) => this.setState({ amount: e.target.value })}></input>
<hr />
<div className='inner'>
<p>How was the Service:- <span>
<select onChange={this.handle}>
<option>Choose...</option>
<option value={20}>Excellent:- 20%</option>
<option value={10}>Moderate:- 10%</option>
<option value={5}>Bad:- 5%</option>
</select>
<input type='text' className='filed' placeholder="Customer Name"
value={this.state.name} onChange={(e) => this.setState({ name: e.target.value })} />
<button type='button' className='filed else' onClick={this.calculate}>Add Customer</button>
</span></p>
</div>
</div>
<div className='Output'>
<p>Customer List:-</p><hr />
<ul className='UL'>
{this.state.name_arr.map((item,index)=>{
return <li key={index}>{item} offering a tip of</li>})}
<span> <ul className='UL'>
{this.state.tip_arr.map((item,index)=>{
return <li key={index}>{item} rupees </li>})}
</ul>
</span>
</ul>
{console.log(this.state.name_arr)}
{console.log(this.state.tip_arr)}
</div>
</>
)
}
};
You're attempting to mutate a state variable. You have to update the value via setState. You should also pass setState a function instead of a plain object in this case so that you can access the correct value since state updates are asynchronous.
tip_change = () => {
this.setState((prevState) => {
const { tip, tip_arr } = prevState;
return {
tip_arr: [...tip_arr, tip]
};
});
}
You can't change state directly.
Do Not Modify State Directly.
Read how to use state correctly from React documentation
To update it, you need to call this.setState eg:
this.tip_change=()=>{
let{tip_arr,tip}=this.state
this.setState(prevState => {
let newTipArray = [prevState.tip_arr, tip]
return {tip_arr: newTipArray }
})
}
This should also be the case for name_arr state. eg:
this.name_change=()=>{
let{name_arr,name}=this.state
//name_arr.push(name) // can't manipulate state directly.
this.setState(prevState => {
let newNameArr = [prevState.name_arr, name]
return {name_arr: newTipArray }
})
}
Update: as #Phishy, using second form of setState() that accepts a function rather than an object helps fix some of state quirks because of the asyncronous nature of React state.
Here is a nice article that explains why to Beware: React setState is asynchronous!
I have placed loader if the button is clicked the loader runs, but its mapped to get value from api so if i click one button the loader runs for all button, how to do it only for corresponding element
my loader is:
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info"> {this.state.loading ? (
<div className="search-loader">
<Loader
visible={this.state.loading}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"}</button>
As the button is mapped in loop loader runs for all button if i click one button,
eg:
You are setting state.loading to true when a button is clicked, I suppose. But in your Loader component you are using that one state.loading variable for each of the buttons and whichever button is clicked, each of the loaders receive the true value for visible.
A solution would be to store the id of the provider in state.providerIdLoading if the provider is loading and for the condition of visible in the Loader you could place state.providerIdLoading === providerList.id. When the provider is finished loading you could set the value to false or whatever is not a providerId.
I think you should change your loading state to something like
const [loading, setLoading] = useState({providerID1: false, providerID2: false, ...})
and your Loader condition will change to
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info"> {this.state.loading[providerList.id] ? (
<div className="search-loader">
<Loader
visible={this.state.loading[providerList.id]}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"}</button>
Here you go with a solution
this.state = {
loading: ""
};
onAddProvider = id => {
this.setState({ loading: id });
}
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info">
{
this.state.loading === providerList.id ? (
<div className="search-loader">
<Loader
visible={this.state.loading === providerList.id}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"
}
</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Instead of having loading as boolean, please use loading as id and match with the current id.
Once loading is done then reset the value to empty string like
this.setState({ loading: "" });
I know this might have some similar questions but I don't seem to able to find the solution for my situation.
I have a form that will be submitted with the content of the child component, the child component is appended onClick and can be appended infinitely. How can I get the value from all the child component, and to post it.
This is index.js
class ListOfProducts extends React.Component {
constructor()
{
super();
this.appendChild = this.appendChild.bind(this);
this.state = {
children: [],
}
}
appendChild() {
this.setState({
children: [
...this.state.children, <NewComponent/>
]
});
}
render() {
return (
<form>
<div>
<pre><h2 className="h2"> Sales Order</h2></pre>
<div className="box" style={{height: '520px', width: '1300px', position: 'relative', overflow: 'auto', padding: '0'}}>
<div style={{height: '1000px', width: '1000px', padding: '10px'}}>
<div>
{this.state.children.map(child => child )}
</div>
</div>
</div>
<button className="addbut" onClick={() => this.appendChild()}>Add Items</button>
</div>
</form>
)
}
}
This is partial code of NewComponent.JS
<select
name="sel"
className="sel"
value={this.state.selecteditems}
onChange={(e) =>
this.setState({selecteditems: e.target.value})}
>
{this.state.data.map(item =>
<option key={item.productID} value={item.unitPrice}>
{item.itemName}
</option>
)}
</select>
{/*unit price*/}
<p>Unit Price: RM {this.state.selecteditems} </p>
{this.state.selecteditems.length ? (
<p>Quantity: </p>
) : null }
{/*button to add quantity*/}
{this.state.selecteditems.length ? (
<button onClick={this.addPro}> + </button>
) : null }
{/*textbox for quantity*/}
{this.state.selecteditems.length ? (
<input type="text" ref="quan" placeholder="Quantity"
value={this.state.quantity}
onChange={(e) =>
this.setState({quantity: e.target.value})}
>
</input>
) : null }
{/*button to decrease quantity}*/}
{this.state.selecteditems.length ? (
<button onClick={this.decPro}> - </button>
) : null }
{/*subtotal*/}
{this.state.selecteditems.length ? (
<p>Sub Total: RM {this.state.subtot} </p>
) : null }
Thanks in advance!
Quick and dumb: add callback like this
<NewComponent onChange={this.onNewComponentChange}/>
(and implement calling of this onChange callback at every change at NewComponent of course)
There are two ways I can think of getting the value from the child component -
Have a state management system (something like redux) which can actually store the data of all the child components. As and when the child component's data changes, it should be synced to the store and the same can be used by the parent to post the data on submit.
Assign ref to each of the child component when it gets appended. save those ref values in a array. Iterate through those references on submit of the form and call some specific getter function of child component to give you its data.
Preferred way is the first method.
I´m trying to change one value of my parent. My parent container send the data to PersonalComponent and PersonalComponent send the data to my AvatarComponent what is the component where I have my input
I receive the props in my PersonalComponent that's I know, but when I send the data to AvatarComponent, I think I can change the data of my container that's the parent of PersonalComponent
This is the parent of all:
class HandlePersonal extends Component {
state = {
...this.props.user
};
handleUsername = e => {
e.preventDefault();
this.props.dispatch({
type: "CHANGE_USERNAME",
payload: this.state.displayName
});
};
handleChange = e => {
this.setState({ ...this.state, [e.target.name]: e.target.value });
alert(2);
};
render() {
return (
<PersonalComponent
handleUsername={this.handleUsername}
handleChange={this.handleChange}
data={this.state}
/>
);
}
}
This is my PersonalComponent
const PersonalComponent = props => {
return (
<div id="personal">
<h2>My profile</h2>
<Card>
<div id="personal-container">
<div id="cover">
<CoverComponent />
</div>
<div id="avatar">
<AvatarComponent
data={props.data.photoURL}
onChange={props.handleChange}
/>
</div>
<div id="user-name">
<form onSubmit={props.handleUsername}>
<InputText
name="displayName"
value={props.data.displayName}
onChange={props.handleChange}
/>
<Button id="save-username" label="Ok" />
</form>
</div>
</div>
</Card>
</div>
);
};
I know that it's working because I had alert(props.data) inside PersonalComponent
And then, this is my AvatarComponent
<h1>Value: {props.data}</h1>
<input name="photoURL" value={props.data} onChange={props.handleChange} />
And here, if I alert(props.data) I receive null that's the actual value of photoURL in the HandlePersonal but I can't change the value using my input. Why?
I want to make that when i write in my input inside AvatarComponent the value of HandleContainer change
In short:
HandlePersonal.handleChange() needs to call this.setState('photoUrl': 'input value') to update the state.
Lemme knwo if you need detailed answer..im on my phone so not so convenient.
EDIT
<input name="photoURL" value={props.data} onChange={props.onChange} />
Because u pass it as onChange
I have a little app that has an input and based on the search value, displays weather for a particular city. I'm stuck at a certain point though. The idea is that once you search a city, it hides the text input and search button and displays some weather info and another search button to search a new city. My issue is that I want to focus on the search box once I click to search again. I hope that makes sense. I read that the ideal way to do this is with refs. I wired it up like such:
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
With this I can pass that ref up to the parent to use in my search by using this.state.myRefs.current.value; It works great, but when I try to reference this.state.myRefs.current in a different function to use .focus(), it returns null.
resetSearch = () => {
console.log(this.state.myRefs.current); // <- returns null
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
Is this because I'm hiding and showing different components based on the search click? I've read numerous posts on SO, but I still can't crack this. Any help is appreciated. I'll include the full code below. To see it in full here is the git repo: https://github.com/DanDeller/tinyWeather/blob/master/src/components/WeatherMain.js
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
recentCities: [],
details: [],
isOpen: true,
myRefs: '',
video: '',
city: ''
};
this.updateInputValue = this.updateInputValue.bind(this);
this.getRefsFromChild = this.getRefsFromChild.bind(this);
this.resetSearch = this.resetSearch.bind(this);
this.getWeather = this.getWeather.bind(this);
}
updateInputValue = (e) => {
...
}
resetSearch = () => {
console.log(this.state.myRefs.current);
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
getWeather = (e) => {
...
}
getRefsFromChild = (childRefs) => {
...
}
render() {
return (
<section className={style.container}>
<div className={style.weatherMain + ' ' + style.bodyText}>
<video key={this.state.video} className={style.video} loop autoPlay muted>
<source src={this.state.video} type="video/mp4">
</source>
Your browser does not support the video tag.
</video>
<div className={style.hold}>
<div className={style.weatherLeft}>
<WeatherForm
updateInputValue={this.updateInputValue}
getWeather={this.getWeather}
passRefUpward={this.getRefsFromChild}
resetSearch={this.resetSearch}
isOpen={this.state.isOpen}
/>
<WeatherList
details={this.state.details}
city={this.state.city}
isOpen={this.state.isOpen}
/>
</div>
<div className={style.weatherRight}>
<Sidebar
recentCities={this.state.recentCities}
/>
</div>
<div className={style.clear}></div>
</div>
</div>
</section>
);
}
}
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
export default Weather;
You try to achieve unmounted component from DOM, because of this you can not catch the reference. If you put this code your instead of render function of WeatherForm component, you can catch the reference. Because i just hide it, not remove from DOM.
render() {
return (
<div>
<div className={style.weatherForm}
style={this.props.isOpen ? {visibility:"initial"} :{visibility:"hidden"}}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
<div className={style.resetButton} style={this.props.isOpen ? {visibility:"hidden"} :{visibility:"initial"}}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
</div>
)
}
console.log(this.state.myRefs.current) returns null , because it's a reference to an input dom element which does not exists as currently Weather form is displaying Search another city along with a reset button.
In reset function state changes, which results in change of prop isOpen for WeatherForm component. Now, screen would be displaying the input field along with search button.
After component is updated ComponentDidUpdate lifecycle method is called.
Please add ComponentDidUpdate lifecycle method in WeatherForm and add ,
this.city.current.focus() in the body of method.
There is no need to pass reference of a dom element to the parent element as it is not consider as a good practise.
Edit 1 :-
Need to set input field in focus only if prop ( isOpen ) is true as we will get reference to the input field only if its mounted.
ComponentDidUpdate(){
if(this props.isOpen)
this.city.current.focus
}
Link to Lifecycle method :-
https://reactjs.org/docs/react-component.html#componentdidupdate
Hope this helps,
Cheers !!