I have made a toaster component of my own which on multiple clicks render multiple toasters. The problem I am facing is that all the toasters are terminated when the handle close component is clicked or when the settimeout function is called. I am passing messages through another component as props.
This is my toaster component
export default class MyToaster extends React.Component {
constructor(props) {
super(props);
this.state = {
message: props.message,
show: false,
no: 0
};
}
handleclose = () => {
this.setState({
show: false,
no: this.state.no - 1
})
}
handleOpen = () => {
console.log('HANDLE OPEN')
this.setState({
show: true,
no: this.state.no + 1
}, () => {
setTimeout(() => {
this.setState({
show: false,
no: this.state.no - 1
})
}, 3000)
})
}
createtoaster = () => {
if (this.state.show) {
let toastmessage = [];
for (let i = 0; i < this.state.no; i++) {
let tmessage = <div className="snackbar">
<div className="card-header">
<h3 className="card-title">Toast</h3>
</div>
<div className="card-body">
{this.state.message}
</div>
<div className="card-footer"></div>
<button className="btn" onClick={this.handleclose}>x</button>
</div>
toastmessage.push(tmessage);
}
return toastmessage;
} else {
return null;
}
};
render() {
return (
<div className="col-md-2 offset-md-9">
<button className="btn btn-primary" onClick={this.handleOpen}></button>
{this.createtoaster()}
</div>
)
}
}
I have tried managing the state in the parent component but it doesnt seem to work. I do know that the problem is in managing state of my toaster component but dont know the exact problem and the solution.
Any solutions for this also feel free to point out any of my mistakes.
TIA
Handle close is run on the click of any button rather on the instance of one of them by the looks of it.
if (this.state.show) { // this determines whether to render you toasts...
// and close turns all of them off.
You need to change each toast to have it's own show property and for close to toggle that one and remove it from the array of toasts to generate.
Note:
Your props and state should be separate, don't copy props into state as this will introduce bugs and changes will not be reflected.
constructor(props) {
super(props);
// avoid copying props into state
// https://reactjs.org/docs/react-component.html#constructor
this.state = {
message: props.message,
show: false,
no: 0
};
}
There is a different way to this approach.
export default class MyToaster extends React.Component {
constructor(props) {
super(props);
this.state = {
message: props.message,
show: true,
no: 0
};
}
componentDidMount() {
setTimeout(() => {
this.setState({show: false})
}, 4000)
}
handleclose = () => {
this.setState({
show: false,
no: this.state.no - 1
})
}
handleOpen = () => {
this.setState({
no: this.state.no + 1
}, () => {
setTimeout(() => {
this.setState({
show: false,
no: this.state.no - 1
})
}, 3000)
})
}
render() {
return (
<div className="col-md-2 offset-md-9">
{this.state.show
? (
<div className="container snackbar" style={this.props.style}>
<div className="card-header">
<h3 className="card-title">Toast</h3>
</div>
<div className="card-body">
{this.props.message}
</div>
<div className="card-footer"></div>
</div>
)
: null
}
</div>
)
}
}
And from your parent component you can include
this.state = {
toasterCollection: []
}
//make a function
handleToasterClick = () => {
const toaster = <Toaster message={this.message} style={this.style}/>
this.setState({
// toasterCollection: [...this.state.toasterCollection, toaster]
toasterCollection: [...this.state.toasterCollection, toaster]
});
}
//In your Render give a button
<button className="btn btn-primary" onClick={this.handleToasterClick}>
Toast
</button>
//Also render this
{this.state.toasterCollection}
This should get your code to work.
Related
Currently, I am working on a simple system where after a button is pushed another webpage opens up. However, I am aiming to display a component after the button is clicked. Hereby, I initialized a state with a boolean set to false. Furthermore, after the button is clicked the state should be set to true which should enable the display of the component ApartmentBooking01. When running the code a new webpage shows up due to the history.push object. However, it does not display my wanted component ApartmentBooking01. Can anyone maybe explain to me what I am doing wrong?
class ApartmentInformation01 extends React.Component {
constructor(props){
super(props);
this.state = {
showBookingInformation: false,
apartmentInformation: [{
apartmentNumber: [],
availableBeds: [],
pricePerNight: []
}]
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.props.history.push("api/apartments/apartmentbooking01");
this.setState({
showBookingInformation: !this.state.showBookingInformation
});
}
getApartmentBookingComponent(){
if(this.state.showBookingInformation){
return <ApartmentBooking01/>
}else {
return null;
}
}
componentDidMount() {
axios.get(`http://localhost:8080/api/apartment/`)
.then(res => {
const apartmentInformation = res.data;
this.setState({ apartmentInformation });
})
}
render() {
const { apartmentInformation } = this.state;
return (
<div className='apartmentInformation-container'>
<ul>
{
apartmentInformation.filter(apartmentInfo => apartmentInfo.apartmentNumber == 1)
.map(filteredApartment => (
<div className='apartmentInformation-items'>
<h2 className='apartmentssection-01-price'>Price per Night</h2>
<p className='apartmentInformation-items-pricePerNight'>€{filteredApartment.pricePerNight},- </p>
<h2 className='apartmentssection-01-beds'>Available Beds</h2>
<p className='apartmentInformation-items-availableBeds'>{filteredApartment.availableBeds} Beds</p>
</div>
))
}
</ul>
<button variant="btn-success" onClick={this.handleClick}>More information</button>
{this.getApartmentBookingComponent}
</div>
)
}
}
export default withRouter (ApartmentInformation01)
Try to add event.preventDefault() at handleClick function. This should be in the first line.
also you should add keys for you map method:
<div className='apartmentInformation-items' key={appId}>
I have two components, one parent (Widgets) and another son (Telefono). The "Telefono" component has the notInCall status and with it I paint or not a certain part of the code.
On the other hand I have the showComponent() function that is in the parent with which I show or not the child component (Telefono) and two other components.
I need to recover from the parent, in the showComponent() function the current status (true or false) of notInCall but I do not know how to do it.
Edit: I think I have not explained well. In the child I use a conditional this.state.notInCall to show or not part of the code. I need to pass the true or false response to the parent. If {this.state.notInCall ? (some code) : (another code)}. If this.state.notInCallon the child is true do one thing and if it is false do another
This is my parent component (Widgets)
class Widgets extends Component {
constructor(props) {
super(props);
this.state = {
componente: 1,
//notInCall: false,
};
this.showComponent = this.showComponent.bind(this);
}
showComponent(componentName) {
/*this.setState({
notInCall: false,
});*/
if(this.state.notInCall === false){
this.setState({
componente: Telefono,
addActive: Telefono,
});
alert(this.state.notInCall + ' running? Componente:' + componentName);
console.log(this.state.notInCall);
}else{
alert('verdad');
this.setState({
componente: componentName,
addActive: componentName,
});
}
console.log(this.state.notInCall);
}
renderComponent(){
switch(this.state.componente) {
case "ChatInterno":
return <ChatInterno />
case "HistorialLlamadas":
return <HistorialLlamadas />
case "Telefono":
default:
return <Telefono showComponent={this.showComponent}/>
}
}
render(){
return (
<div id="bq-comunicacion">
<nav>
<ul>
<li><button onClick={() => this.showComponent('Telefono')} id="mn-telefono" className={this.state.addActive === 'Telefono' ? 'active' : ''}><Icon icon="telefono" className='ico-telefono'/></button></li>
<li><button onClick={() => this.showComponent('ChatInterno')} id="mn-chat" className={this.state.addActive === 'ChatInterno' ? 'active' : ''}><Icon icon="chat-interno" className='ico-chat-interno'/></button></li>
<li><button onClick={() => this.showComponent('HistorialLlamadas')} id="mn-llamadas" className={this.state.addActive === 'HistorialLlamadas' ? 'active' : ''}><Icon icon="historial-llamadas" className='ico-historial-llamadas'/></button></li>
</ul>
</nav>
<div className="content">
{ this.renderComponent() }
</div>
</div>
);
}
}
This is my child component (Telefono)
class Telefono extends Component {
constructor(props) {
super(props);
this.inputTelephone = React.createRef();
["update", "reset", "deleteClickNumber", "closeAlert", "handleKeyPress",].forEach((method) => {
this[method] = this[method].bind(this);
});
this.state = this.initialState = {
notInCall: true,
isRunning: false,
};
}
phoneCall(e){
e.preventDefault();
this.props.showComponent(this.state.notInCall);
if(this.state.inputContent.length < 2){
this.setState({
warningEmptyPhone: true,
});
this.change = setTimeout(() => {
this.setState({
warningEmptyPhone: false
})
}, 5000)
}else if(this.state.inputContent.length >= 2 ){
this.setState({
notInCall: !this.state.notInCall,
isRunning: !this.state.isRunning,
componente: 'Telefono',
},
() => {
this.state.isRunning ? this.startTimer() : clearInterval(this.timer);
//console.log(this.componente);
});
}
}
render(){
return(
<div className="pad sb-content">
{this.state.notInCall
? (
<>
<div className="dial-pad">
<div className="digits">
<Numbers numbers={this.state.numbers}
/>
</div>
</div>
<div className="btn-call call" onClick={this.phoneCall.bind(this)}>
<Icon icon="telefono" className='ico-telefono'/>
<span>LLAMAR</span>
</div>
</>
)
: (
<div className="call-pad">
<div id="ca-number" className="ca-number">{this.state.inputContent}</div>
<TimeElapsed id="timer" timeElapsed={timeElapsed}/>
</div>
)}
</div>
);
}
}
Thanks for the help
You can create a handle in parent, like:
handleNotInCall (notInCall) {
// handle here
}
And pass this handle to child:
<Telefono handleNotInCall={this.handleNotInCall} />
In child you call like this:
this.props.handleNotInCall(<param here>)
UPDATE
on parent:
On Parent:
put notInCall in state
create a handle for notInCall
pass for child handle and state
// state
this.state = {
componente: 1,
notInCall: false,
};
// create a handle
handleNotInCall (notInCall) {
this.setState({notInCall});
}
// pass both for child
<Telefono handleNotInCall={this.handleNotInCall} notInCall={this.state.notInCall}/>
In child, where you do:
this.setState({
notInCall: !this.state.notInCall,
isRunning: !this.state.isRunning,
componente: 'Telefono',
})
// change for
this.props.handleNotInCall(!this.props.notInCall)
this.setState({
isRunning: !this.state.isRunning,
componente: 'Telefono',
})
// where you use for compare
this.state.notInCall ?
// change for:
this.props.notInCall ?
If I understood your problem correctly, the answer of Marcello Silva is correct.
Say you have this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
childAvailable: true,
};
}
handleAvailability = status => {
this.setState({ childAvailable: status });
}
render() {
const { childAvailable } = this.state;
if (!childAvailable) {
return (...); // display whatever you want if children not available
} else {
return (
<div>
{children.map(child => {
<Children
child={child}
statusHandler={this.handleAvailability}
/>
}
</div>
);
}
}
}
and
class Children extends React.Component {
constructor(props) {
super(props);
this.state = {
available: true,
};
}
handleClick = e => {
const status = !this.state.available;
this.setState(prevState => { available: !prevState.available });
// if you call the handler provided by the parent here, with the value
// of status, it will change the state in the parent, hence
// trigger a re render conditionned by your value
this.props.statusHandler(status);
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click me to change status</button>
</div>
);
}
}
Doing this will call the function you passed as prop to your children in your parent. This function sets your state, and therefore triggers a re render once the state has been set, so you'll be rendering whatever you want considering the new value of childAvailable.
EDIT: After seeing the comment on said answer, I'd like to add that you can of course call your handleClick method from your conditions on this.state.notInCall.
So I know how to change state when the button is clicked once, but how would I change the new state back to the previous state when the button is clicked again?
You can just toggle the state.
Here's an example using a Component:
class ButtonExample extends React.Component {
state = { status: false }
render() {
const { status } = this.state;
return (
<button onClick={() => this.setState({ status: !status })}>
{`Current status: ${status ? 'on' : 'off'}`}
</button>
);
}
}
Here's an example using hooks (available in v16.8.0):
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<button onClick={() => setStatus(!status)}>
{`Current status: ${status ? 'on' : 'off'}`}
</button>
);
};
You can change the 'on' and 'off' to anything you want to toggle. Hope this helps!
Here is my example of show on toggle by using React Hook without using useCallback().
When you click the button, it shows "Hello" and vise-versa.
Hope it helps.
const IsHiddenToggle = () => {
const [isHidden, setIsHidden] = useState(false);
return (
<button onClick={() => setIsHidden(!isHidden)}>
</button>
{isHidden && <p>Hello</p>}
);
};
Consider this example: https://jsfiddle.net/shanabus/mkv8heu6/6/
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
buttonState: true
}
this.toggleState = this.toggleState.bind(this)
}
render() {
return (
<div>
<h2>Button Toggle: {this.state.buttonState.toString()}</h2>
<button onClick={this.toggleState}>Toggle State</button>
</div>
)
}
toggleState() {
this.setState({ buttonState: !this.state.buttonState })
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Here we use a boolean true/false and flip between the two states. If you are looking to use some other custom data as your previous state, just create a different variable for that.
For example:
this.state = { previousValue: "test", currentValue: "new thing" }
This will toggle to previous and new value :
constructor() {
super();
this.state = {
inputValue: "0"
}
}
render() {
return (
<div>
<input
type="button"
name="someName"
value={this.state.inputValue}
onClick={() =>
this.state.inputValue === "0"
? this.setState({
inputValue: "1"
})
:
this.setState({
inputValue: "0"
})
}
className="btn btn-success"
/>
</div>
)
}
Description :
If the current value = 0, then set the value to 1, and vice versa.
This is useful if you have a lot of inputs. So, each input has a different state or condition.
You must save the previous state. You could even make previous state part of your actual state - but I'll leave that as an exercise for the OP (Note: you could preserve a full history of previous states using that technique). Unfortunately I cannot yet write examples from the top of my head using the new hooks feature:
class MyComponent extends ReactComponent {
prevState = {}
state = {
isActive: false,
// other state here
}
handleClick = () => {
// should probably use deep clone here
const state = Object.assign({}, this.state);
this.setState(state.isActive ? this.prevState : Object.assign(state, {
isActive: true,
// other state here
});
this.prevState = state;
}
render() {
return <button onClick={this.handleClick}>Toggle State</button>
}
}
in state:
this.state = {toggleBtn: ""}
in your button:
<button key="btn1" onClick={() => this.clickhandler(btn1)}>
{this.state.toggleBtn === ID? "-" : "+"}
</button>
in your clickhandler:
clickhandler(ID) {
if (this.state.toggleBtn === ID) {
this.setState({ toggleBtn: "" });
} else {
this.setState({ toggleBtn: ID});
}
For this application, clicking a listed item once should create a button component underneath this listed item. Clicking the button should cause this listed item to be deleted.
I am currently facing difficulty trying to 'delete' the listed item after the button is clicked. Here is the code that went wrong (this is found in CountdownApp component) :
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList) // countdownList array is correct
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns) // this.state.countdowns does not match countdownList
console.log(countdownList) // countdownList array is still correct
})
}
In the code above, I removed the item to be deleted from countdownList array with splice and tried to re-render the app with setState. However, the new state countdowns do not reflect this change. In fact, it returns the unedited state.
I have also tried the following:
handleDelete(index) {
this.setState({
countdowns: [] // just using an empty array to check if setState still works
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
In the code above, I tried setting state to be an empty array. The console log for this.state.countdowns did not print out an empty array. It printed out the unedited state again
This is the only event handler that isn't working and I have no idea why (main question of this post) :/
If I have 'setstate' wrongly, why does the other 'setState' in other parts of my code work?? (I would like to request an in-depth explanation)
This is all my code for this app (its a small app) below:
import React from 'react'
import ReactDOM from 'react-dom'
class DeleteButton extends React.Component {
render() {
return (
<ul>
<button onClick={this.props.onDelete}>
delete
</button>
</ul>
)
}
}
class Countdown extends React.Component {
render () {
//console.log(this.props)
return (
<li
onClick={this.props.onClick}
onDoubleClick={this.props.onDoubleClick}
>
{this.props.title} - {this.props.days}, {this.props.color}
{this.props.showDeleteButton ? <DeleteButton onDelete={this.props.onDelete}/> : null }
</li>
)
}
}
const calculateOffset = date => {
let countdown = new Date(date)
let today = new Date
let timeDiff = countdown.getTime() - today.getTime()
let diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24))
return diffDays
}
class CountdownList extends React.Component {
countdowns() {
let props = this.props
// let onClick = this.props.onClick
// let onDoubleClick = this.props.onDoubleClick
let rows = []
this.props.countdowns.forEach(function(countdown, index) {
rows.push(
<Countdown
key={index}
title={countdown.title}
days={calculateOffset(countdown.date)}
color={countdown.color}
showDeleteButton={countdown.showDeleteButton}
onDelete={() => props.onDelete(index)}
onClick={() => props.onClick(index)}
onDoubleClick={() => props.onDoubleClick(index)}
/>
)
})
return rows
}
render() {
return (
<div>
<ul>
{this.countdowns()}
</ul>
</div>
)
}
}
class InputField extends React.Component {
render() {
return (
<input
type='text'
placeholder={this.props.placeholder}
value={this.props.input}
onChange={this.props.handleInput}
/>
)
}
}
class DatePicker extends React.Component {
render() {
return (
<input
type='date'
value={this.props.date}
onChange={this.props.handleDateInput}
/>
)
}
}
class CountdownForm extends React.Component {
constructor(props) {
super(props)
this.state = {
title: this.props.title || '',
date: this.props.date || '',
color: this.props.color || ''
}
}
componentWillReceiveProps(nextProps) {
this.setState({
title: nextProps.title || '',
date: nextProps.date || '',
color: nextProps.color || ''
})
}
handleSubmit(e) {
e.preventDefault()
this.props.onSubmit(this.state, this.reset())
}
reset() {
this.setState({
title: '',
date: '',
color: ''
})
}
handleTitleInput(e) {
this.setState({
title: e.target.value
})
}
handleDateInput(e) {
this.setState({
date: e.target.value
})
}
handleColorInput(e) {
this.setState({
color: e.target.value
})
}
render() {
return (
<form
onSubmit={(e) => this.handleSubmit(e)}
>
<h3>Countdown </h3>
<InputField
placeholder='title'
input={this.state.title}
handleInput={(e) => this.handleTitleInput(e)}
/>
<DatePicker
date={this.state.date}
handleDateInput={(e) => this.handleDateInput(e)}
/>
<InputField
placeholder='color'
input={this.state.color}
handleInput={(e) => this.handleColorInput(e)}
/>
<button type='submit'>Submit</button>
</form>
)
}
}
class CountdownApp extends React.Component {
constructor() {
super()
this.state = {
countdowns: [
{title: 'My Birthday', date: '2017-07-25', color: '#cddc39', showDeleteButton: false},
{title: 'Driving Practice', date: '2017-07-29', color: '#8bc34a', showDeleteButton: false},
{title: 'Korean BBQ', date: '2017-08-15', color: '#8bc34a', showDeleteButton: false}
]
}
}
handleCountdownForm(data) {
if (this.state.editId) {
const index = this.state.editId
let countdowns = this.state.countdowns.slice()
countdowns[index] = data
this.setState({
title: '',
date: '',
color: '',
editId: null,
countdowns
})
} else {
data.showDeleteButton = false
const history = this.state.countdowns.slice()
this.setState({
countdowns: history.concat(data),
})
}
}
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList)
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
handleCountdown(index) {
const countdownList = this.state.countdowns.slice()
let countdown = countdownList[index]
countdown.showDeleteButton = !countdown.showDeleteButton
this.setState({
countdowns: countdownList
})
}
handleDblClick(index) {
const countdownList = this.state.countdowns
const countdown = countdownList[index]
this.setState({
title: countdown.title,
date: countdown.date,
color: countdown.color,
editId: index
})
}
render() {
return (
<div>
<CountdownForm
title={this.state.title}
date={this.state.date}
color={this.state.color}
onSubmit={(data) => {this.handleCountdownForm(data)}}
/>
<CountdownList
countdowns={this.state.countdowns}
onDelete={(index) => this.handleDelete(index)}
onClick={(index) => this.handleCountdown(index)}
onDoubleClick={(index) => this.handleDblClick(index)}
/>
</div>
)
}
}
ReactDOM.render(
<CountdownApp />,
document.getElementById('app')
)
I managed to find the answer to my own question!
setState worked as expected. The bug was due to <li> container that wrapped the event handler.
Clicking <li> causes it to call onClick event (which is managed by handleCountdown function in CountdownApp component) which causes it to setState.
As the delete button was wrapped in <li> container, clicking the delete button calls 2 event listeners - handleCountdown and handleDelete. handleCountdown is called twice in this case, once from clicking <li> to expand and the next call when the delete button is clicked.
There is a high chance that the last async setState dispatched from handleCountdown overwrites handleDelete's setState. Hence, the bug.
Here is changes: (I recoded everything again so the names might differ a little but the logic stays the same)
class Countdown extends React.Component {
render () {
return (
<li>
<div onClick={this.props.onClick} > // Add this div wrapper!
{this.props.title} - {this.props.days}, {this.props.color}
</div>
{this.props.toShow ?
<ButtonsGroup
onDelete={this.props.onDelete}
onEdit={this.props.onEdit}
/>
: null}
</li>
)
}
}
So the solution is to separate the clickable area and the buttons. I added a div wrapper over the text in <li> so whenever the text in <li> is clicked, the added <ul> will be out of onClick event handler area.
I have the following:
import React from 'react';
import axios from 'axios';
class FirstName extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
};
}
getName () {
var name = this.refs.firstName.value;
this.setState(function() {
this.props.action(name);
});
}
handleSubmit (e) {
e.preventDefault();
this.setState({ submitted: true }, function() {
this.props.actionID(2);
this.props.activeNav('color');
});
}
render () {
return (
<div>
<h2>tell us your first name</h2>
<form>
<input
type="text"
ref="firstName"
onChange={this.getName.bind(this)}
/>
<div className="buttons-wrapper">
<button href="#">back</button>
<button onClick={this.handleSubmit.bind(this)}>continue</button>
</div>
</form>
</div>
);
}
};
class PickColor extends React.Component {
backToPrevious (e) {
e.preventDefault();
this.props.actionID(1);
this.props.activeNav('name');
}
goToNext (e) {
e.preventDefault();
this.props.actionID(3);
this.props.activeNav('design');
this.props.displayIconsHolder(true);
}
getColorValue(event) {
this.props.color(event.target.getAttribute("data-color"));
}
render () {
var colors = ['red', 'purple', 'yellow', 'green', 'blue'],
colorsLink = [];
colors.forEach(el => {
colorsLink.push(<li
data-color={el}
key={el}
onClick={this.getColorValue.bind(this)}
ref={el}>
{el}
</li>
);
});
return (
<section>
<ul>
{colorsLink}
</ul>
<button onClick={this.backToPrevious.bind(this)}>back</button>
<button onClick={this.goToNext.bind(this)}>continue</button>
</section>
);
}
}
class ConfirmSingleIcon extends React.Component {
goBack () {
this.props.goBack();
}
confirmCaptionandIcon (event) {
var optionID = event.target.getAttribute("data-option-id"),
name = event.target.getAttribute("data-option-name");
this.props.setOptionID(optionID);
this.props.setIcon(1, name, optionID, false);
}
goNext () {
this.props.goNext();
}
render () {
console.log(this.props.currentState);
var options = [],
that = this;
this.props.iconOptionsList.forEach(function(el){
options.push(<li onClick={that.confirmCaptionandIcon.bind(that)} key={el.option} data-option-name={el.option} data-option-id={el.id}>{el.option}</li>);
});
return (
<div>
<h2>Choose your caption</h2>
<h3>
{this.props.selectedIcon}
</h3>
<ul>
{options}
</ul>
<button onClick={this.goBack.bind(this)} >back</button>
<button onClick={this.goNext.bind(this)} >confirm</button>
</div>
);
}
}
class ConfirmCaption extends React.Component {
handleClick () {
var currentState = this.props.currentState;
this.props.setIcon(currentState.icon_ID, currentState.selectedIcon, currentState.option_ID, true);
this.props.setIconVisiblity(true);
this.props.setIconListVisiblity(false);
}
render () {
console.log(this.props.currentState);
return (
<div>
<p onClick={this.handleClick.bind(this)}>confirm icon and caption</p>
</div>
);
}
}
class ChooseIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
setOptionID (id) {
this.setState({ option_ID: id })
}
setIconVisiblity (onOff) {
this.setState({ confirmIcon: onOff })
}
setIconListVisiblity (onOff) {
this.setState({ iconList: onOff })
}
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
handleClick (event) {
var iconId = event.target.getAttribute("data-icon-id"),
that = this;
this.state.icons.forEach(function(el){
if(el.id == iconId){
that.setState(
{
confirmIcon: true,
iconList: false,
selectedIcon: el.name,
icon_ID: iconId,
selectedIconOptions: el.option
}
);
}
});
}
goBack () {
this.setState(
{
confirmIcon: false,
iconList: true
}
);
}
goNext () {
this.setState(
{
confirmIcon: false,
iconList: false,
confirmCaption: true
}
);
}
render () {
var icons = [];
this.state.icons.forEach(el => {
icons.push(<li data-icon-id={el.id} onClick={this.handleClick.bind(this)} key={el.name}>{el.name}</li>);
});
return (
<div>
{this.state.iconList ? <IconList icons={icons} /> : ''}
{this.state.confirmIcon ? <ConfirmSingleIcon goBack={this.goBack.bind(this)}
goNext={this.goNext.bind(this)}
setIcon={this.props.setIcon}
selectedIcon={this.state.selectedIcon}
iconOptionsList ={this.state.selectedIconOptions}
setOptionID={this.setOptionID}
currentState={this.state} /> : ''}
{this.state.confirmCaption ? <ConfirmCaption currentState={this.state}
setIcon={this.props.setIcon}
setIconVisiblity={this.setIconVisiblity}
setIconListVisiblity={this.setIconListVisiblity} /> : ''}
</div>
);
}
}
class IconList extends React.Component {
render () {
return (
<div>
<h2>Pick your icon</h2>
<ul>
{this.props.icons}
</ul>
</div>
);
}
}
class Forms extends React.Component {
render () {
var form;
switch(this.props.formID) {
case 1:
form = <FirstName action={this.props.action} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 2:
form = <PickColor displayIconsHolder={this.props.seticonsHolder} color={this.props.colorVal} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 3:
form = <ChooseIcon setIcon={this.props.setOptionA} />
break;
}
return (
<section>
{form}
</section>
);
}
}
export default Forms;
"ChooseIcon" is a component that will get used 3 times therefore everytime I get to it I need to bring its state back as if it was the first time.
Ideally I would need to make this ajax call everytime:
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
is there a way to manually call componentDidMount perhaps from a parent component?
React handles component lifecycle through key attribute. For example:
<ChooseIcon key={this.props.formID} setIcon={this.props.setOptionA} />
So every time your key (it can be anything you like, but unique) is changed component will unmount and mount again, with this you can easily control componentDidMount callback.
If you are using the ChooseIcon component 3 times inside the same parent component, I would suggest you to do the ajax in componentDidMount of the parent component like this (exaclty how you have in your example, in terms of code)
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
and then pass this data down to the ChooseIcon component
render() {
return (
//do your stuff
<ChooseIcon icons={this.state.icons}/>
)
}
after this you will only need to set the received props in your ChooseIconcomponent, for that you only need to change one line in it's constructor:
constructor(props) {
super(props);
this.state = {
icons: props.icons, // Changed here!
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
The parent component can use a ref to call the function directly.
However, trying to force this function feels like a smell. Perhaps lifting the state higher up the component tree would solve this problem. This way, the parent component will tell ChooseIcon what to show, and there will not be a need to call componentDidMount again. Also, I assume the Ajax call can also occur once.