I have an svg button that contains children elements(polygons). The handleMouseLeave function is used to set the state and then utilize its callback to update the children immediatley after. Even though the 'mouseleave' and 'callback' logs both fire in order, the rest of the code inside the callback area does not always fire.
Is there a better or correct way to handle the callback?
handleMouseLeave = (e) => {
console.log('mouseleave')
const polygons = [...e.target.children];
this.setState({
translate: "translateX(-100%)",
opacity: 0
}, () => {
console.log('callback')
polygons.forEach(child => {
child.style.transform = this.state.translate;
child.style.opacity = this.state.opacity;
});
});
};
--
render() {
return(
<button>
<HeroButtonSVG
id="HeroButton"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
/>
<span>Check out my work<i className="fas fa-chevron-right"></i></span>
</button>
);
};
This seems to be an event binding issue, as class methods are not bound by default.
To solve this, you can add the following to the render function, which will bind the event, through using arrow functions to automatically bind:
<HeroButtonSVG
id="HeroButton"
onMouseEnter={e => this.handleMouseEnter(e)}
onMouseLeave={e => this.handleMouseLeave(e)}
/>
It turns out that the actual issue was derived from the pointer-events on the child SVG's. The issue was not related to React's setState callback function.
I tracked the issue here: https://github.com/facebook/react/issues/4492
Related
I have a simple button on page with onClick event, and then useState hook that is by default set to false. Then I have elements on page, when I click on them I wanna do an action, but only if the state of that useState hook is set to true, which is done by if condition, but for some reason it passes it, even though the state should be false. My code:
Initiating the hook:
const [ isEraser, setIsEraser ] = useState(false);
Setting state on button click:
<span
className="scroll"
onClick={() => {
setIsEraser((isEraser) => !isEraser);
}}
>
The condition:
console.log('Eraser is set to', isEraser);
if (isEraser == true) {
// Code
}
The output of the console.log:
no changing state (is default false) -> false
changing state to true -> true
changing state back to false and triggering that event in which the condition is -> false, true, false
Edit:
I can't pass full component code, because it's kinda big, and some parts of code are protect by client contract, but the condition is in onClick event:
$body.on('click', '.tvs-annotated-text, .tvs-annotate-element', function(
e
) {
console.log('Eraser is set to', isEraser);
if (isEraser === true) {
setIsAlert(1);
// Odstraníme vybranému elementu backround
this.style.backgroundColor = '';
this.classList.remove('tvs-annotated-text');
this.classList.remove('tvs-annotate-element');
}
});
pretty sure you just need to do setIsEraser(!isEraser)
You don't have to use a function to set state setIsEraser((isEraser) => !isEraser); → setIsEraser(!isEraser)
Allright, so the problem was that the onClick trigger was written in jQuery, after rewriting it to normal javascript it works now. Big thanks to you guys.
In the setIsEraser you need to pass the last state. you passed the initial state in it so every time you press the button it passes false as isEraser.
change the isEraser in setIsEraser function to something else like pre or previous, etc.
<span
className="scroll"
onClick={() => {
setIsEraser((pre) => !pre);
}}
>
In this way pre would be the last state not the initial state and your program will work just fine.
i am working on a project with a react.js FE, a Node/Express.js BE and a database. I am currently working on a function which trigger my delete Route in BE. But my function trigger with every load and onlick, but should only trigger onClick.
Here are code samples of my service and my FE component. I am new to react.js so help would be apprechiated.
hardwareService.js:
static deleteHardware(hardwareId) {
console.log(hardwareId);
return axios.delete(hostname + '/hardware/' + hardwareId)
.then(response => {
return response;
})
}
component:
deleteOnClick = (id) => {
console.log('handleDelete wird ausgeführt!' + id);
HardwareService.deleteHardware(id);
}
html:
<tbody>
{this.state.hardware.map(hardware => (
<tr key={hardware.id}>
<td>{hardware.id}</td>
<td>{hardware.producer}</td>
<td>{hardware.model}</td>
<td>{hardware.type}</td>
<td>{hardware.serial_number}</td>
<td>{hardware.price}</td>
<td>{<Button variant="primary">Bearbeiten</Button>}</td>
<td>{<Button variant="primary" onClick=
{this.deleteOnClick(hardware.id)}>Löschen</Button>}</td>
</tr>
))}
</tbody>
Replace this line:
onClick={this.deleteOnClick(hardware.id)}>Löschen</Button>}
With this:
onClick={() => this.deleteOnClick(hardware.id)}>Löschen</Button>}
Explanation: in your current code you are calling the function immediately (when rendering the component) then passing the result as an event handler which has no effect. So you need to encapsulate the function call in another function for future call (arrow functions are well suited for that).
The hardware.id parameter will be enclosed in that function thanks to the JavaScript closure mechanism.
I have this piece of code.
Where the problem I am facing is that is missing a prop which I want to be an id, obtained inside the map function.
Since I am unable to find a way to send the id to the component outside map, I figured I would do this:
This is my render function
render() {
var users = this.state.userList;
const Table = Reactable.Table,
Td = Reactable.Td,
Tr = Reactable.Tr;
if (users.length === 0) {
return <p>loading</p>
}
return (
<div class="maincontainer">
<div className="content-landing">
<Table
className="table"
filterable={['Email']}
itemsPerPage={8}
currentPage={0}
sortable={true}
>
{users.map((row) => {
return (
<Tr className={row.className}>
<Td column="Email">{row.email}</Td>
<Td column="Edit" ><FontAwesomeIcon className="editIcon" onClick={this.showModal(row.id)} icon={faEdit} /></Td> //----THIS.SHOWMODAL IS BEING AUTOEXECUTED WITHOUT ACCOUNTING FOR THE CLICKING
</Tr>
)
})}
</Table>
<EditUserModal show={this.state.showModal} close={this.closeModal} row={this.state.rowId}/> //---I WANT THIS TO RECEIVE A row.id FROM THE ABOVE MAP
</div>
</div>
)
}
I have written a couple comments in the parts where I want to make the changes.
I also thought about putting <EditUserModal> , inside the <Td> , but since its an external npm module it crashes, it only admits text.
So my solution was, to pass the row.id to the showModal function, and try to set is a state from there.
showModal(rowId) {
// console.log("showmodal state before any click")
// console.log(this.state.showModal)
console.log("triggered show modal")
console.log(rowId)
this.setState({
showModal: true,
rowId: rowId
}, () => {
// console.log("clicked show modal")
// console.log(this.state.showModal)
});
}
I think it should work, but since the function has a () , it auto executes without waiting for any click, making my app to crash.
In your code you are executing the function directly: this.showModal(row.id).
You have to use a callBack function to pass row.id something like:
onClick={()=> this.showModal(row.id)}
This should solve your problem.
You can use a callBack function in the onClick like this:
onClick={()=> this.showModal(row.id)}
Or you can define the showModal function like this:
showModal = (rowId) => () => {
...
}
and onClick={this.showModal(row.id)}
For people who does not understand arrow functions, the above solution is similar to this:
function showModal(rowId) {
return function () {
...
}
}
The showModal function returns another function that will be triggered in the onClick event.
I have just come accross with an issue related to event listening in Vue directives.
I have a component which holds following code inside:
function setHeaderWrapperHeight() { ... }
function scrollEventHandler() { ... }
export default {
...
directives: {
fox: {
inserted(el, binding, vnode) {
setHeaderWrapperHeight(el);
el.classList.add('header__unfixed');
window.addEventListener(
'scroll',
scrollEventListener.bind(null, el, binding.arg)
);
window.addEventListener(
'resize',
setHeaderWrapperHeight.bind(null, el)
);
},
unbind(el, binding) {
console.log('Unbound');
window.removeEventListener('scroll', scrollEventListener);
window.removeEventListener('resize', setHeaderWrapperHeight);
}
}
}
...
}
And this component is re-rendered everytime I change router path, I achieved this behaviour by assigning current route path to :key prop so whenever path changes it gets re-rendered. But the propblem is though event listeners are not being removed/destroyed causing terrible performance issues. So how do I remove event listeners?
Calling bind on a function creates a new function. The listeners aren't being removed because the function you're passing to removeEventListener is not the same function you passed to addEventListener.
Communicating between hooks in directives is not particularly easy. The official documentation recommends using the element's dataset, though that seems clumsy in this case:
https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments
You could just store the listeners on the element directly as properties so that they're available in the unbind hook.
The code below takes a slightly different approach. It uses an array to hold all of the elements that are currently bound to the directive. The listener on window is only ever registered once, no matter how many times the directive is used. If the directive isn't currently being used then that listener is removed:
let foxElements = []
function onClick () {
console.log('click triggered')
for (const entry of foxElements) {
clickHandler(entry.el, entry.arg)
}
}
function clickHandler (el, arg) {
console.log('clicked', el, arg)
}
new Vue({
el: '#app',
data () {
return {
items: [0]
}
},
directives: {
fox: {
inserted (el, binding) {
console.log('inserted')
if (foxElements.length === 0) {
console.log('adding window listener')
window.addEventListener('click', onClick)
}
foxElements.push({
el,
arg: binding.arg
})
},
unbind (el, binding) {
console.log('unbind')
foxElements = foxElements.filter(element => element.el !== el)
if (foxElements.length === 0) {
console.log('removing window listener')
window.removeEventListener('click', onClick)
}
}
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<button #click="items.push(Math.floor(Math.random() * 1000))">Add</button>
<hr>
<button
v-for="(item, index) in items"
v-fox:example
#click="items.splice(index, 1)"
>Remove {{ item }}</button>
</div>
However, all of this assumes that a directive is even the right way to go. If you can just do this at the component level then it may get a lot simpler because you have the component instance available to store things. Just remember that calling bind creates a new function, so you'll need to keep a reference to that function somewhere so you can pass it to removeEventListener.
Just for the records, and to help whoever passes through here as there is already an accepted answer, what one could do in this case (on Vue 3 at least, not tested on Vue 2) is to use binding.dir (which is a reference to the directive's own object) to host the function for adding the event listener on the directive object and take it back later when there's a need to remove this listener.
One simple example (not related to the original question) for binding one focus event:
export default {
...
directives: {
fox: {
handleFocus: () => { /* a placeholder to rewrite later */ },
mounted(el, binding) {
binding.dir.handleFocus = () => { /* do whatever */ }
el.addEventListener('focus', binding.dir.handleFocus);
},
beforeUnmount(el, binding) {
el.removeEventListener('focus', binding.dir.handleFocus);
}
}
}
...
}
A practical example of what I'm doing with this, in my case, is to have a focus/blur notifier for any input or textarea tag. I made a Gist here of this, it is on a project built on Vue 3 with TypeScript.
I am in an event function and I would like to create a new alert popup (I am using the react-portal library):
onNewAlert: function(username) {
var divModal = (
<Portal ref={'Portal'+username}>
<div id={'div'+username}>
<br /><br/ >Alert for {username}
</div>
</Portal>);
...
}
But then I would have to call a function that is inside a Portal. I could normally do this with references if I was in the render() function, but I am in an event.
this.refs['Portal'+username].openPortal(); // openPortal is a function of the Portal component
Is there a way to call a component function for a component created on the fly in a javascript function?
Even if you could call portal.openPortal() it wouldn't do anything since the component created in the event handler wouldn't be attached to the DOM.
Instead of trying to render the Portal in the event handler function, the event handler should change the component state which will trigger render().
onNewAlert: function(username) {
this.setState({ showAlert: true });
}
The render() function would then use the state variable for the Portal component's isOpened property:
render: function () {
return (
<div>
...
<Portal isOpened={this.state.showAlert}>
...
</Portal>
</div>
);
}