I am making a react button component and the component looks like this
const Button = props => {
return (
<button
style={props.style}
onClick={props.onClick}
className={`btn ${props.color} ${props.loading &&
"loading"} ${props.block && "block"} ${props.className}`}
disabled={props.disabled}
type={props.type}
value={props.value}
>
<span className={"content"}>
{props.icon && <span className={"icon-left"}>{props.icon}</span>}
{props.children}
</span>
{props.loading ? <Spinner /> : null}
</button>
);
};
The issue arises when i try to add a value prop and an event listener to the button component.
If i do that and i try getting the event.target.value value from the onClick handler, it gets to work only when i click other parts of the button, clicking the span text returns an undefined
This seems logical since there is no value on the span, what is the best way i could use to fix this?
You should use event.currentTarget instead of event.target
The currentTarget event property returns the element whose event
listeners triggered the event.
More on reading here.
Use event.currentTarget.
Add an attribute data-value to the span. So on click get the event.currentTarget.value or event.currentTarget.dataset.value
Inside the handler do like this
let m = event.currentTarget.value?
event.currentTarget.value:
event.currentTarget.dataset.value
Related
Greetings
I have built a search and every time user types word it renders new checkboxes but new checkboxes don't work like they used to be none of the event listeners work on new checkboxes, when I'm clicking on checkboxes they just don't react, but in old ones, until search will render this they are working normally
//search in checkbox data
const checkOptions = (container, value, containerId) => {
for (let i = 0; i < props.unique[containerId].length; i++) {
let item = props.unique[containerId][i];
if (
props.unique[containerId][i] !== null &&
props.unique[containerId][i].includes(value)
) {
element = (
<label
onClick={(e) => {e.stopPropagation(); ifAnyChecked(e);}} key={i}>
<input onClick={(e) => {tableSearch(e);}} type="checkbox" value={item ? item : "empty"}/>
{item && item.length > 28 ? (
handleCheckbox(item)
) : (
<p>{item}</p>
)}
</label>
);
tempData += ReactDOMServer.renderToString(element);
}
}
container.innerHTML = tempData;
};
any idea what's happening?
Have you tried to use onChange event instead of onClick? As far as I know, input type checkbox doesn't have such an event like onClick.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox
I used to get this problem when I was working with Vanilla JS whenever i render a new element then that element was not triggering my events. That was because they were generated on runtime so the event wasn't bound to that element. Now I think that thing is happening here as well. So I changed your code and put it inside a state now it is working. I hope I helped. Do let me know if this is not the solution that you were looking for but it solves your problem though
I put the html inside a state array then i mapped it out inside the newCheckBox div. I changed the input to controlled input with fieldValue state. Lastly i changed the new checkbox alert from onClick={alert("doesn't goes in")} to onClick={() => alert("I think its working now right?")}
Here is the complete code sandbox
https://codesandbox.io/s/polished-sea-vedvh?file=/src/App.js
My project start with VueJS and Buefy.
The component have two different actions click :
Click on Cyan area -> redirection to other page (Action 1)
Click on Magenta area -> show dropdown (Action 2)
But when I click to Action 2, always Action 1 works.
Here my component :
<MyComponent
:projects="data"
#click.native="actionOne()"
/>
And inside my component, I have dropdown (using Buefy component) :
<p>{{ data.projects }}</p>
<BDropdown aria-role="list">
<BButton
slot="trigger"
class="button"
type="is-text"
#click.prevent="actionTwo()"
>
<BIcon icon="dots-horizontal" />
</BButton>
<BDropdownItem aria-role="listitem">Update</BDropdownItem>
<BDropdownItem aria-role="listitem">Archive</BDropdownItem>
</BDropdown>
I try to use different event modifier but I can't have the expected behavior :
stop
prevent
This probably happens because of event bubbling. When you click on the dropdown element, the click event bubbles up to the cyan area. What you need to do is cancel the event bubbling for the dropdown element.
<BDropdown aria-role="list">
<BButton
slot="trigger"
class="button"
type="is-text"
#click="actionTwo($event)"
>
<BIcon icon="dots-horizontal" />
</BButton>
<BDropdownItem aria-role="listitem">Update</BDropdownItem>
<BDropdownItem aria-role="listitem">Archive</BDropdownItem>
</BDropdown>
<script>
export default {
methods: {
actionTwo(e) {
e.cancelBubble = true
}
}
}
</script>
I found the solution. The problem came to specific event from Dropdown component (Buefy). So I added stop modifier to Dropdown trigger click event and I added prevent in my component.
Here the solution :
<MyComponent
:projects="data"
#click.native.prevent="actionOne()"
/>
<BDropdown aria-role="list" #click.native.stop>
<BButton
slot="trigger"
class="button"
type="is-text"
>
<BIcon icon="dots-horizontal" />
</BButton>
<BDropdownItem aria-role="listitem">Update</BDropdownItem>
<BDropdownItem aria-role="listitem">Archive</BDropdownItem>
</BDropdown>
You can use the self modifier.
<MyComponent
:projects="data"
#click.native.self="actionOne()"
/>
As the documentation says:
only trigger handler if event.target is the element itself
i.e. not from a child element
(source)
So I have this form that updates the search in real time via storeUserSearch, which is why a value isn't set on input. I'm trying to implement an icon that deletes what's in the input field on click, and also runs the action storeUserSearch('').
The second onClick has no problems by itself. I'm simply trying to get the first onClick to work, and then have them both running at the same time so my click executes both.
<form>
<input
className="searchBox fas fa-search"
type="search"
placeholder="Search"
onChange={event => this.props.storeUserSearch(event.target.value) } />
<i className="fas fa-times-circle" onClick={event => event.target.parentElement.input} onClick={event => this.props.storeUserSearch('')}> </i>
</form>
Edit: Figured out how to reset the value via
onClick={event => event.target.parentElement.firstChild.value=''}
Just need to figure out how to combine the onClicks now.
why you're writing two onClick on same element? just pass a method on its click event and write all the logic there.
i.e:
<i className="fas fa-times-circle" onClick={this.handleIconClick}> </i>
// and in handleIconClick function write the logic.
handleIconClick = (e)=>{
e.target.parentElement.firstChild.value='';
this.props.storeUserSearch('');
}
You can't define two onClick props, one will overwrite the other. You can try something like
onClick={(event) => {event.target.parentElement.firstChild.value=''; this.props.storeUserSearch('')}}
note the semi-colon between the two individual statements
You can only have 1 onClick attribute per component, not multiple. I'm not sure exactly what storeUserSearch function is supposed to do since it's omitted from your code snippet, but based on what you've explained, I think you can achieve what you want to do similar to below:
class SearchForm extends React.Component {
constructor () {
super();
this.state = {
inputValue: ''
};
}
render() {
return (
<div>
<input
type="text"
value={ this.state.inputValue }
onChange={
e => {
this.setState({inputValue: e.target.value})
}
}
/>
<input
type="button"
value="Clear input"
onClick={
() => {
this.setState({inputValue: ''})
}
}/>
<span style={{ display: 'block' }}>Current textbox state: <span style={{ color: 'blue' }}>{this.state.inputValue}</span></span>
</div>
);
}
}
ReactDOM.render(<SearchForm></SearchForm>, document.querySelector('#app'));
<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>
<div id="app"></div>
Basically we create a component that is made up of a 2 input elements (1 text and 1 button). The approach I took was to simply set the value attribute equal to our component's state.inputValue, which is updated each time a change event occurs on it.
We just hardcode the state.inputValue property to '' when the clear button is pressed.
The buttons i create using below seems to lag in the selectedButtonIdx value.
Is the toggleSelected not complete by the time getClass is called ?
function ButtonGroup(props) {
const [selectedButtonIdx,setIdx]=useState(props.loadCurrentAsIndex);
const toggleSelected = (e) => {
setIdx(parseInt(e.target.dataset.index));
props.onclick(e);
};
const getClass = (index) => {
return (selectedButtonIdx === index) ? classnames('current', props.btnClass)
: classnames(props.btnClass)
};
let buttons = props.buttons.map((b, idx) => <Button key={idx} value={b.value} index={idx} text={b.text}
onclick={e => toggleSelected(e)}
btnClass={getClass(idx)}/>);
return (
<div>
{buttons}
</div>
);
}
Every onclick is expected to show the user which button in the group was clicked by changing its class.
By looking at this,
<Button
key={idx}
value={b.value}
index={idx}
text={b.text}
onclick={e => toggleSelected(e)}
btnClass={getClass(idx)}
/>
Button is your custom component,
Two things to notice here,
You have provided onclick (c is small) props, in you actual component it should be onClick={props.onclick}
You have used e.target.dataset.index, to work with dataset we should have attribute with data- prefix. So your index should be data-index in your actual component.
So finally your Button component should be,
const Button = (props) => {
return <button text={props.text} data-index={props.index} onClick={props.onclick} className={props.btnClass}>{props.value}</button>
}
Demo
The function setIdx, returned from useState is asynchronous, this means that it may be not be finished by the time you run your next function (as you guessed).
Take a look at useEffect it allows you to specify a function to run once an item in your state changes, this method will ensure your functions are called in the right order.
By now I don't see anything wrong here.
How it works:
initial render happens, onClick event listener is bound
user clicks a button, event handler calls setIdx triggering new render
new render is initiated, brand new selectedButtonIdx is used for rendering(and for getClass call as well)
See, there is no reason to worry about if setIdx is sync function or async.
I am trying to toggle the colours of two buttons in ReactJS. I can set the active state property of the selected button OK, but I can't work out how to change the style of another button (calcY) based on my selection (of calcX).
The code is brittle but I am pretty new to react and any pointers on best practices would be appreciated. PS also I am using react-bootstrap for the Form and buttons.
const MyForm = React.createClass({
handleChange(event, attribute) {
let eventValue = event.target.value;
if (attribute === 'calcX'){
this.setState({active: true});
this.setState({bsStyle: 'info'});
let calcXBtn = ReactDOM.findDOMNode(this.refs.calcBtnGroup.refs.calcX);
calcXBtn.setState({bsStyle: 'default'});
}
...
}
render() {
return (
<Form onSubmit={this.handleSubmit} horizontal>
<FormGroup>
<ButtonGroup ref="calcBtnGroup">
<Button active className='btn btn-info' ref="calcX" onClick={(event) => this.handleChange(event, 'calcX')}>Calculate X</Button>
<Button className='btn btn-default' ref="calcY" onClick={(event) => this.handleChange(event, 'calcY')}>Calculate Y</Button>
</ButtonGroup>
...
);
}
});
module.exports = MyForm;
You can set the className or style based of an element (or subcomponent) on the state of your component. It's nice to use a ternary operator and ES6 template literals here.
<Button ref="calcX" className=`btn ${this.state.active ? 'btn-info' : 'btn-default'}` onClick={(event) => this.handleChange(event, 'calcX')}>Calculate X</Button>
What this does, is setting a className based on the state of your component. The <Button> component always has a btn className. If state.active is true, the class btn-info will be added. Otherwise btn-default will be added.
So the only thing you have to do now, is set the state in your handleChange method and the classNames will be rendered appropriately.
Edit: it's not really necessary to use refs here. It's almost never necessary to use refs. You want use React events (onChange, onSubmit, etc.) to set input values on the state, and render those values in the value in your inputs. These are called controlled components. You can read more about it in the official documentation: https://facebook.github.io/react/docs/forms.html