I'm trying to use this.$refs.cInput.focus() (cInput is a ref) and it's not working. I'd be able to hit g and the input should pop up and the cursor should focus in it, ready to input some data. It's showing but the focus part is not working. I get no errors in the console.
Vue.component('coordform', {
template: `<form id="popup-box" #submit.prevent="process" v-show="visible"><input type="text" ref="cInput" v-model="coords" placeholder =""></input></form>`,
data() {
{
return { coords: '', visible: false }
}
},
created() {
window.addEventListener('keydown', this.toggle)
},
mounted() {
},
updated() {
},
destroyed() {
window.removeEventListener('keydown', this.toggle)
},
methods: {
toggle(e) {
if (e.key == 'g') {
this.visible = !this.visible;
this.$refs.cInput.focus() //<--------not working
}
},
process() {
...
}
}
});
You can use the nextTick() callback:
When you set vm.someData = 'new value', the component will not
re-render immediately. It will update in the next “tick”, when the
queue is flushed. [...]
In order to wait until Vue.js has finished updating the DOM after a data
change, you can use Vue.nextTick(callback) immediately after the data
is changed. The callback will be called after the DOM has been
updated.
(source)
Use it in your toggle function like:
methods: {
toggle(e) {
if (e.key == 'g') {
this.visible = !this.visible;
this.$nextTick(() => this.$refs.cInput.focus())
}
}
}
In my case nextTick does not worked well.
I just used setTimeout like example below:
doSearch () {
this.$nextTick(() => {
if (this.$refs['search-input']) {
setTimeout(() => {
this.$refs['search-input'].blur()
}, 300)
}
})
},
I think that for your case code should be like below:
toggle(e) {
if (e.key == 'g') {
this.visible = !this.visible;
setTimeout(() => { this.$refs.cInput.focus() }, 300)
}
}
Not sure if this is only applicable on Vue3 but if you want to show focus on an input box from inside a component, it's best for you to setup a transition. In the transition, there is an event called #after-enter. So after the animation transition on enter is completed, the #after-enter is executed. The #after-enter will be the event that will always get executed after your component shows up.
I hope this helps everyone who are having issues with the:
***this.$nextTick(() => this.$refs.searchinput.focus());***
It may not be working the way you wanted it to because chances are the initial focus is still on the parent and the input element has not been displayed yet or if you placed it under mounted, it only gets executed once and no matter how you show/hide the component (via v-if or v-show), the nextTick line was already executed on mount() and doesn't get triggered on show of the component again.
Try this solution below:
<template>
<transition name="bounce" #after-enter="afterEnter" >
<div>
<input type="text" ref="searchinput" />
</div>
</transition>
</template>
<script>
methods: {
afterEnter() {
setTimeout(() => {
this.$refs.searchinput.focus();
}, 200);
},
}
</script>
Related
I have 2 event listeners defined in the mounted lifecycle of a component, that allow a user to close out of a v-dialog by either double clicking outside or pressing escape. Writing these vanilla event listeners was the only way I knew to achieve the functionality that I wanted. Something like this:
<template>
<v-container>
<v-dialog :value="value" max-width="55vw" height="70vw" persistent>
...some stuff...
</v-dialog>
</v-container>
</template>
<script>
export default {
name: "a name",
props: {
value: { required: true },
},
mounted() {
let that = this;
document.addEventListener("keyup", function (evt) {
if (evt.key === "Escape") {
that.closeDialog();
}
});
document.addEventListener("dblclick", function (e) {
if (e.target == document.getElementsByClassName("v-overlay__scrim")[0]) {
that.closeDialog();
}
});
},
unmounted() {
window.removeEventListener("keyup");
window.removeEventListener("dblclick");
},
methods: {
closeDialog() {
this.$emit("input");
},
},
};
</script>
I would like to extend this functionality of closing out of v-dialogs to several other components, but adding these event listeners to multiple components' mounted block seems redundant. Is there a better way to achieve this?
In my opinion, you shouldn't use native events for this case. Also adding an event listener over the document on an specific Vue component, could lead to confusions, since you're always listening to that event everywhere (in your case, "esc" key being pushed). Imagine if you have a large number of components and when you push "esc", some code is being executed. If you're not the author of this piece of code, it's "hard" to tell from where it comes.
For this case I would recommend: using a Mixin that could contain the lifecycle hooks (vue 2) or using a composable for importing the function (vue 2 with vue/composition-api or vue 3) and manually registering it with the proper lifecycle hook function.
BTW, on your code, on the remove event listener part, you should pass the function reference. So it would be better to extract the method to a proper vue method of that component.
mounted() {
let that = this;
document.addEventListener("keyup", this.close);
document.addEventListener("dblclick", this.close2);
},
unmounted() {
window.removeEventListener("keyup", this.close);
window.removeEventListener("dblclick", this.close2);
},
methods: {
close(evt) {
if (evt.key === "Escape") {
this.$emit("input");
}
},
close2(e) {
if (e.target == document.getElementsByClassName("v-overlay__scrim")[0]) {
this.$emit("input");
}
},
},
If I run this code:
const state = reactive({
title: '',
})
watchEffect(() => {
console.log(state.title)
})
watchEffect is triggered and the console is outputting an empty string:
""
If I want to assign a new value to state.title, watchEffect is triggered twice. Is this behaviour expected or am I doing something wrong?
According to the documentation, watchEffect
Runs a function immediately [emphasis added] while reactively tracking its dependencies
and re-runs it whenever the dependencies are changed.
So it is expected that it should run twice in this situation: once when it is first defined, and then again when the value changes.
As #MykWillis pointed out and is clearly stated in the docs, watchEffect runs immediately and on subsequent changes, exactly as watch with { immediate: true } would.
If you do not want the effect to run initially, don't use watchEffect, use watch instead:
const { createApp, watchEffect, reactive, toRefs, watch } = Vue;
createApp({
setup() {
const state = reactive({
title: 'test',
});
watchEffect(() => {
console.log('watchEffect', state.title)
});
watch(() => state.title, () => {
console.log('watch', state.title)
});
watch(() => state.title, () => {
console.log('watch.immediate', state.title)
}, { immediate: true })
return { ...toRefs(state) }
}
}).mount('#app')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<input v-model="title">
</div>
I have a event binding from the window object on scroll. It gets properly fired everytime I scroll. Until now everything works fine. But the setNavState function (this is my setState-function) does not update my state properties.
export default function TabBar() {
const [navState, setNavState] = React.useState(
{
showNavLogo: true,
lastScrollPos: 0
});
function handleScroll(e: any) {
const currScrollPos = e.path[1].scrollY;
const { lastScrollPos, showNavLogo } = navState;
console.log('currScrollPos: ', currScrollPos); // updates accordingly to the scroll pos
console.log('lastScrollPos: ', lastScrollPos); // last scroll keeps beeing 0
if (currScrollPos > lastScrollPos) {
setNavState({showNavLogo: false, lastScrollPos: currScrollPos});
} else {
setNavState({showNavLogo: true, lastScrollPos: currScrollPos});
}
}
useEffect(() => {
window.addEventListener('scroll', handleScroll.bind(this));
}, []);
...
}
So my question is how do I update my state properties with react hooks in this example accordingly?
it's because how closure works. See, on initial render you're declaring handleScroll that has access to initial navState and setNavState through closure. Then you're subscribing for scroll with this #1 version of handleScroll.
Next render your code creates version #2 of handleScroll that points onto up to date navState through closure. But you never use that version for handling scroll.
See, actually it's not your handler "did not update state" but rather it updated it with outdated value.
Option 1
Re-subscribing on each render
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
});
Option 2
Utilizing useCallback to re-create handler only when data is changed and re-subscribe only if callback has been recreated
const handleScroll = useCallback(() => { ... }, [navState]);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
Looks slightly more efficient but more messy/less readable. So I'd prefer first option.
You may wonder why I include navState into dependencies but not setNavState. The reason is - setter(callback returned from useState) is guaranteed to be referentially same on each render.
[UPD] forgot about functional version of setter. It will definitely work fine while we don't want to refer data from another useState. So don't miss up-voting answer by giorgim
Just add dependency and cleanup for useEffect
function TabBar() {
const [navState, setNavState] = React.useState(
{
showNavLogo: true,
lastScrollPos: 0
});
function handleScroll(e) {
const currScrollPos = e.path[1].scrollY;
const { lastScrollPos, showNavLogo } = navState;
console.log('showNavLogo: ', showNavLogo);
console.log('lastScrollPos: ', lastScrollPos);
if (currScrollPos > lastScrollPos) {
setNavState({showNavLogo: false, lastScrollPos: currScrollPos});
} else {
setNavState({showNavLogo: true, lastScrollPos: currScrollPos});
}
}
React.useEffect(() => {
window.addEventListener('scroll', handleScroll.bind(this));
return () => {
window.removeEventListener('scroll', handleScroll.bind(this));
}
}, [navState]);
return (<h1>scroll example</h1>)
}
ReactDOM.render(<TabBar />, document.body)
h1 {
height: 1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
I think your problem could be that you registered that listener once (e.g. like componendDidMount), now every time that listener function gets called due to scroll, you are referring to the same value of navState because of the closure.
Putting this in your listener function instead, should give you access to current state:
setNavState(ps => {
if (currScrollPos > ps.lastScrollPos) {
return { showNavLogo: false, lastScrollPos: currScrollPos };
} else {
return { showNavLogo: true, lastScrollPos: currScrollPos };
}
});
Vue.js allow apply event on element:
<div id="app">
<button #click="play()">Play</button>
</div>
But how to apply event on window object? it is not in DOM.
for example:
<div id="app">
<div #mousedown="startDrag()" #mousemove="move($event)">Drag me</div>
</div>
in this example, how to listen mousemove event on window ?
You should just do it manually during the creation and destruction of the component
...
created: function() {
window.addEventListener('mousemove',this.move);
},
destroyed: function() {
window.removeEventListener('mousemove', this.move);
}
...
Jeff's answer is perfect and should be the accepted answer. Helped me out a lot!
Although, I have something to add that caused me some headache. When defining the move method it's important to use the function() constructor and not use ES6 arrow function => if you want access to this on the child component. this doesn't get passed through to arrow functions from where it's called but rather referrers to its surroundings where it is defined. This is called lexical scope.
Here is my implementation with the called method (here called keyDown instead of move) included:
export default {
name: 'app',
components: {
SudokuBoard
},
methods: {
keyDown: function () {
const activeElement = document.getElementsByClassName('active')[0]
if (activeElement && !isNaN(event.key) && event.key > 0) {
activeElement.innerHTML = event.key
}
}
},
created: function () {
window.addEventListener('keydown', this.keyDown)
},
destroyed: function () {
window.removeEventListener('keydown', this.keyDown)
}
}
To be extra clear, The method below doesn't have access to this and can't for example reach the data or props object on your child component.
methods: {
keyDown: () => {
//no 'this' passed. Can't access child's context
}
You can also use the vue-global-events library.
<GlobalEvents #mousemove="move"/>
It also supports event modifiers like #keydown.up.ctrl.prevent="handler".
This is for someone landed here searching solution for nuxt.js:
I was creating sticky header for one of my projects & faced issue to make window.addEventListener work.
First of all, not sure why but window.addEventListener doesn't work with created or beforeCreate hooks, hence I am using mounted.
<template lang="pug">
header(:class="{ 'fixed-header': scrolled }")
nav menu here
</template>
<script>
export default {
name: 'AppHeader',
data() {
return { scrolled: false };
},
mounted() {
// Note: do not add parentheses () for this.handleScroll
window.addEventListener('scroll', this.handleScroll);
},
methods: {
handleScroll() {
this.scrolled = window.scrollY > 50;
},
},
};
</script>
I found that using window.addEventListener no longer worked as I expected with Vue 3. It appears that the function is wrapped by Vue now. Here's a workaround:
...
created()
{
document.addEventListener.call(window, "mousemove", event =>
{
...
});
}
...
The important part is document.addEventListener.call(window, what we're doing is to take the unwrapped addEventListener from document and then call it on the window Object.
I encountered this problem building a webapp and I replicated it in this jsfiddle. Essentially, I would like an input to call this.setState({message: input_val}) every time I type something into it, then pass it into the parent App class which then re-renders the message onto the Message class. However the output seems to always be one step behind what I actually type. The jsfiddle demo should be self explanatory. I am wondering if I did anything wrong to prompt this.
html
<script src="http://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id='app'></div>
js
var App = React.createClass({
getInitialState: function() {
return {message: ''}
},
appHandleSubmit: function(state) {
this.setState({message: state.message});
console.log(this.state.message);
},
render: function() {
return (
<div className='myApp'>
<MyForm onChange={this.appHandleSubmit}/>
<Message message={this.state.message}/>
</div>
);
}
});
var MyForm = React.createClass({
handleSubmit: function() {
this.props.onChange(this.state);
},
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value});
this.handleSubmit();
},
render: function() {
return (
<form className="reactForm" onChange={this.handleChange}>
<input type='text' />
</form>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div className="message">
<p>{this.props.message}</p>
</div>
)
}
});
React.render(
<App />,
document.getElementById('app')
);
A call to setState isn't synchronous. It creates a "pending state transition." (See here for more details). You should explicitly pass the new input value as part of the event being raised (like to handleSubmit in your example).
See this example.
handleSubmit: function(txt) {
this.props.onChange(txt);
},
handleChange: function(e) {
var value = e.target.value;
this.setState({message: value});
this.handleSubmit(value);
},
There is a much simpler way to do this, setState(updater, callback) is an async function and it takes the callback as second argument,
Simply pass the handleSubmit as a callback to setState method, this way after setState is complete only handleSubmit will get executed.
For eg.
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit);
}
Try to change the handleChange() method like above and it will work.
for syntax of using setState check this link
with setState hook
useEffect(() => {
your code...
}, [yourState]);
I was pulling my hair out for like an hour because of this so I decided to share... If your callback is still one step behind and seemingly not working, ensure you don't CALL the function with parenthesis... Just pass it in. Rookie mistake.
RIGHT:
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit);
}
VS
WRONG:
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit());
}
There's no reason for MyForm to be using state here. Also putting the onChange on the form instead of the input you're interested in is odd. Controlled components should be preferred because their behavior is more obvious, and any time App's message state changes (even if you e.g. allow Message to change it later), it'll be correct everywhere.
This also makes your code a bit shorter, and considerably simpler.
var App = React.createClass({
getInitialState: function() {
return {message: ''}
},
appHandleSubmit: function(message) {
this.setState({message: message});
},
render: function() {
return (
<div className='myApp'>
<MyForm onChange={this.appHandleSubmit}
message={this.state.message} />
<Message message={this.state.message}/>
</div>
);
}
});
var MyForm = React.createClass({
handleInputChange: function(e){
this.props.onChange(e.target.value);
},
// now always in sync with the parent's state
render: function() {
return (
<form className="reactForm">
<input type='text' onChange={this.handleInputChange}
value={this.props.message} />
</form>
);
}
});
jsbin
Knowing the problem is with not having asyncronous behaviour of setState I solved my issue with async await
onChangeEmail=async x =>{
await this.setState({email:x})
}
You could refactor your class-based component to a functional component as someone else mentioned. The drawbacks are that this can be quite time-consuming depending on how many code lines you have to refactor and is likely prone to error.
I will use your example of a changeHandler to display how it could be done in a functional component.
const INITIAL_DATA = {
message: ""
}
const [form, setForm] = useState({...INITIAL_DATA})
const changeHandler = (e) = setForm({
...form,
[e.target.name]: e.target.value
})
<InputField name={message} value={form.message} onChange={changeHandler}>
^ The above code will produce that behavior as you explained of onChange being one step behind the setState. As others have said, this is due to the asynchronous nature of the setState hook.
The way to solve this is to use the useEffect hook, which allows you to introduce state dependencies. So when setState is finished going through its update cycle the useEffect will fire. So add the below to the above code and BAM.. it should show the state changes without a step delay.
useEffect(() => {
doSomeValidationHere()
orSendOffTheForm()
}, [form])
*Notice how we add a dependency array after the useEffect(() => {}) hook.
Extra info:
If the dependency array is empty it will only run on component mount and first render
If there is no array, it will run every time the page renders
If there is a state name in the array it will run every time that state is finished setting
I found it very cumbersome for me to define 3 handler functions just to get some value to a component's state, so I decided not to use state at all. I just defined an additional property to the component that stored desired value.
So I ended up with a code that looked something like this:
//...
},
text: '',
handleChange: function(event) {
this.text = event.target.value;
this.forceUpdate();
},
render: function() {
return <div>
<InputComponent onChange={this.handleChange}/>
<DisplayComponent textToDisplay={this.text}/>
</div>
}
//...
or as in my case - just use onKeyUp, instead of down...
There are some solutions mentions above, some of them have some problems, but
ajsaule is the correct ansower. Here is show my Code in TypeScript Example:
// solve the problem: setstate always one step behind
useEffect(() => { isFormValid(fields, setFieldsError) }, [fields])
const handleChange = (field: string ) => (evt: React.ChangeEvent<HTMLInputElement>) => {
setFields({ ...fields, [field]: evt.target.value })
}
const isFormValid = (fields: FieldsState, setFieldsError: React.Dispatch<React.SetStateAction<TempObj>>) => {
const tempObj: TempObj = {}
const { email, password } = fields
if( !isEmail(email) ) tempObj.email = 'Invalid Email Address'
if( password.length < 8 ) tempObj.password = 'password must by atleast 8 character long'
Object.keys(fields).forEach(field => {
if(!fields[field as keyof FieldsState]) tempObj[field] = `'${field}' is emapty`
})
setFieldsError(tempObj)
return Object.values(tempObj).every( item => item == '' )
}