I am passing the option values into a series of Dropdown buttons, each of which is in a child component from a data array.
When an option is chosen in one of the buttons I am updating the state in the parent component with the result of onSelect. This is all working fine...
//parent component
sourceSelected = (event) => {
this.setState({
sourceSelected: event
});
...
<ButtonToolbar>
{MEDIUM.map((medium) =>
<Medium key={medium.medium_name} medium={medium} onSelectedValue{this.sourceSelected } />
)};
</ButtonToolbar>
//child component
<DropdownButton title={props.medium.medium_name} id="source-dropdown" onSelect={props.onSelectedValue}>
{props.medium.source.map((option, index) =>
<MenuItem key={index} eventKey={option}> {option} </MenuItem>)}
</DropdownButton>
However, I would also like to store in the state (mediumSelected=???) the name of the button from which the option was selected.
Is there anyway to get OnSelect to pass this back or should I do something else?
OK, I answered this using... https://reactjs.org/docs/handling-events.html passing arguments to event handlers.
The code is:
//parent component
sourceSelected = ( medium_name, event) => {
this.setState({
sourceSelected: event,
mediumSelected: medium_name
});
}
...
<div className='test'>
<ButtonToolbar>
{MEDIUM.map((medium) =>
<Medium key={medium.medium_name} medium={medium} onSelectedValue={this.sourceSelected.bind(this, medium.medium_name) } />
)};
</ButtonToolbar>
You can take advantage of Javascript events and this. Basically, pass the event to the function that will be using the button name, like this
<button name="btn" onClick={e => this.buttonName(e.target.name)}>
You will also need to bind this in your constructor()
Example code:
constructor(props) {
super(props);
// Bind this so you can use it below
this.buttonName = this.buttonName.bind(this);
}
buttonName(e) {
console.log(e);
}
render() {
return (
<div>
// Pass the name of the button to the function
<button name="btn" onClick={e => this.buttonName(e.target.name)}>
Button
</button>
</div>
);
}
I also threw a quick example on https://codesandbox.io/s/lrwqr303vz. Don't mind the file names.
Related
I'm curious to what's a more performant way of handling list logic. For example...
The Item component
export default function Item({data}) => {
return <div>
<h1>{data.name}
<button> Do Something </button>
</div>
}
The List component
export default function List({list}) => {
return <div>
{list.map(item) => <Item data={item} />}
</div>
}
The Main component
export default function Main() {
return <div>
<List list={someList} />
</div>
}
If i want to do something with the button in the Item component is it better to place the logic inside the Item component like this:
export default function Item({data}) => {
const handleDoSomething = () => {
logic goes here
}
return <div>
<h1>{data.name}
<button onClick={() => handleDoSomething()}> Do Something </button>
</div>
}
Or should i propagate back the event to the List component and handle it there like this:
export default function Item({data, handleButtonClick}) => {
return <div>
<h1>{data.name}
<button onClick={() => handleButtonClick()}> Do Something </button>
</div>
}
export default function Main() {
const handleDoSomething = () => {
logic goes here
}
return <div>
<List list={someList} handleButtonClick={() => handleDoSomething()} />
</div>
}
What's the better to do this ? I want a better performance.
I guess that propagating back the event to the List component is better because the handler function will only be declared once, instead of once for every button that you render.
And btw, if you won't pass any custom argument to your handler function, you don't need to create an arrow function, you can just do:
<button onClick={handleButtonClick}> Do Something </button>
I'm making a custom dropdown list in reactjs. When I click on any element in the dropdown list I get it's value and put it inside an input and it works just fine. The problem is that the first element returns nothing and I can't get it's value. Also I developed the dropdown list to disappear when I choose any element inside of it. But like I said it works just fine on all elements except the first one.
I solved the problem by setTimeOut and hide the dropdown list in 50 milliseconds. But I don't think this's a right solution.
//Select Component
class Select extends Component {
showList = (e) => {
e.target.closest('.select').classList.add('active');
}
hideList = (e) => {
setTimeout(() => {
e.target.closest('.select').classList.remove('active');
}, 100);
}
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
}
render() {
return (
<input
{...this.props}
type="text"
placeholder={this.props['placeholder']}
onFocus={this.showList}
onBlur={this.hideList}
value={this.state.selectValue}
onChange={this.changeSelectValue}
required
/>
<div className="select">
<div className="select-options menu full-width">
{
this.props.list.map(element => {
return (
<MenuItem text={element} onClick={(e) => this.selectValue(e)} />
)
})
}
</div>
</div>
);
}
}
==================
class MenuItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<p className={"menu-item " + this.props['type'] } {...this.props}>
{this.props['icon'] ? <i className={this.props['icon']} ></i> : null}
<span>{this.props['text']}</span>
</p>
);
}
}
1.use key prop for every MenuItem Component when you are using the list to create a list of MenuItems.
2.Instead of getting value from target in selectValue function directly pass the value from onClick handler.
selectValue = (e , element) => {
this.setState({ selectValue: element })
}
<MenuItem text={element} key={element} onClick={(e) => this.selectValue(e , element)} />
Editted:-
Remove the onBlur handler and put the functionality of hideList inside selectValue function after setState,you can use setState with callback if normal setState doesn't work
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
e.target.closest('.select').classList.remove('active');
}
I'm trying to create a simple dashboard. I'm just exploring some new ideas I have in react and it's been so long I'm running into a strange problem I can't seem to understand.
I have a very simple class:
export default class Dashboard extends React.Component {
constructor(){
super();
}
HandleClick = (e) => {
if (e.name === "createEvent") {
console.log('event clicked');
}
console.log(e.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={this.HandleClick}/>
</div>
)
}
}
and then I have a simple function outside of the class that creates a button list:
function ButtonList(props) {
return (
<button name="createEvent" onClick={props.HandleClick}>Create Event</button>
)
}
the idea behind this was instead of having so much stuff inside one superclass I wanted to separate simple functionality, like a button or command list if you will, that opon clicking would eventually change the state of the navbar.
I'm not sure how I would return that values of the button, or aside from that pass a parameter into the button from a child prop.
For example instead of doing HandleClick = (e) => and actually look for a parameter, how would I pass that in the child function where it gets used (if there were many more buttons)?
This is what you should be doing instead:
On your parent component, you can use arrow functions to pass the parameters within handleClick. This will allow you to listen to the events on your child ButtonList component with the parameters passed onto the method.
In addition, if you want to access to name attribute of your button, you should be calling event.target.name, as name is part of the target property of the Event interface.
export default class Dashboard extends React.Component {
constructor(){
super();
}
handleClick = (e) => {
if (e.target.name === "createEvent") {
console.log('event clicked');
}
console.log(e.target.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={(e) => this.handleClick(e)} />
</div>
)
}
}
And on your ButtonList functional component, you should pass the onClick event to the onClick props which was defined as part of the ButtonList component.
function ButtonList(props) {
const onClick = (e) => {
props.onClick(e);
};
return (
<button name="createEvent" onClick={(e) => onClick(e)}>Create Event</button>
)
}
I have created a demo over here.
I'm trying to disable the edit button once i click on complete but it is not working. I have passed in the state in disabled attribute but it seems not doing anything, don't know maybe because of setState's asynchronous nature. I passed callback while calling setState method and it seems logging data randomly, Can someone suggest what should be done ?
class App extends Component {
state = {
buttons: {
id: "test"
}
};
handleCheckBox = id => {
let buttons = Object.assign({}, this.state.buttons);
buttons.id = !this.state.buttons[id]
this.setState({buttons}, ()=>console.log(this.state.buttons));
}
render() {
return (
<div>
{todos.map(todo => (
<List key={todo.id}>
<ListItem
role={undefined}
dense
button
>
<Checkbox
onClick={()=>this.handleCheckBox(todo.id)}
checked={todo.complete}
tabIndex={-1}
disableRipple
/>
<ListItemText primary={todo.text} />
<ListItemSecondaryAction>
<Button mini color="secondary" variant="fab" disabled={this.state.buttons[todo.id]}>
<Icon>edit_icon</Icon>
</Button>
ListItemSecondaryAction>
</ListItem>
</List>
))}
</div>
);
}
}
Instead of using id to change the state use index of Array to update the state
Create an array in Component state which tracks the disabled attribute of each buttons
state = {
buttons: Array(todos.length).fill(false),
};
In componentDidMount initialise the array according to todos
componentDidMount(){
const buttons=this.state.buttons.slice();
for(var i=0;i<buttons.length;i++)
buttons[i]=todos[i].complete;
this.setState({buttons:buttons})
}
Now use the value in buttons state for disabled attribute of button based on the index of the component being rendered.
<Button mini color="secondary" variant="fab"
disabled={buttons[todos.indexOf(todo)]}>
Whenever CheckBox is clicked pass the index to the handleChange function and update the value corresponding to the index value
<Checkbox
onClick={() =>this.handleCheckBox(todos.indexOf(todo))}
checked={buttons[todos.indexOf(todo)]}{...other}
/>
handleCheckBox = index => {
const buttons=this.state.buttons.slice();
buttons[index] = !buttons[index];
this.setState({
buttons:buttons
})
}
I have a form with type="range". Now I would like to add 3 buttons that change the same value that the form does. For some reason, the buttons onClick event seems to get called repeatedly upon calling the render function.
This is my component:
class Slider extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleButton = this.handleButton.bind(this);
}
handleChange() {
this.props.onSetCountdown(parseInt(this.refs.seconds.value, 10));
}
handleButton(value) {
this.props.onSetCountdown(parseInt(value, 10));
}
render() {
return(
<div>
<form className={styles.ttSlider} ref="form">
<input max="480" min="60" name="slider" onChange={this.handleChange} ref="seconds" type="range" value={this.props.totalSeconds}/>
<button onClick={this.handleButton(60)}>1min</button>
<button onClick={this.handleButton(180)}>3min</button>
<button onClick={this.handleButton(300)}>5min</button>
</form>
</div>
)
}
}
Slider.propTypes = {
totalSeconds: React.PropTypes.number.isRequired,
onSetCountdown: React.PropTypes.func.isRequired
};
And this is from the parent component:
handleSetCountdown(seconds) {
this.setState({
count: seconds
});
}
From the parent component render:
<Slider totalSeconds={count} onSetCountdown={this.handleSetCountdown}/>
This is the error that I get:
Warning: setState(...): Cannot update during an existing state
transition (such as within render or another component's
constructor). Render methods should be a pure function of props and
state; constructor side-effects are an anti-pattern, but can be moved
to componentWillMount.
To me this looks like the buttons onClick gets called while the component is still rendering. What am I doing wrong?
It's because instead of passing the function to the event onClick, you're calling the function directly.
Try doing it this way:
<button onClick={() => { this.handleButton(60)}}>1min</button>
<button onClick={() => { this.handleButton(180)}}>3min</button>
<button onClick={() => { this.handleButton(300)}}>5min</button>
Found the answer here: React onClick function fires on render
Hope it helps!
If you dont want to use anon functions for any reason, the second method is to use bind directly at render function. Then you can delete lines at your constructor :)
<button onClick={this.handleButton.bind(this, 60)}>1min</button>