Using querySelector() in vue Compostion API - vuejs3

I'm new to Vue composition API and need to use the Document method querySelector. However, it is not working as expected. If i write
<nav class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = document.querySelector(".home-menu");
observer.observe(target);
console.log(target);
target is null. Reading docs I see the ref attribuute and if I
<nav ref="navbar" class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const target = ref("navbar");
console.log(target);
I do console an object. Is ref the way that you get a DOM element in composition API? Can I now use target in my observer object? Is it equivalent to querySelector? I tried this
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = ref("navbar");
observer.observe(target);
but got error
Uncaught TypeError: IntersectionObserver.observe: Argument 1 does not implement interface Element.
Thanks.

The reason document.querySelector is returning null is that the DOM does not contain that element yet.
The script setup runs when the component is created, so the component's DOM has not been created yet.
You can use the onMounted lifecycle hook to run code when the component has been mounted. I created a playground to demonstrate this.
From the Lifecycle docs:
For example, the onMounted hook can be used to run code after the component has finished the initial rendering and created the DOM nodes
There are then two approaches to achieve what you want. You can use continue to use querySelector, or you can use template refs.
Personally, I use template refs for static elements (such as navbars, etc.) and querySelector for dynamic selections.
Using Template Refs
A template ref is a regular Vue ref, but by connecting it to an element or child component via the ref attribute you can obtain a direct reference to that element or component.
If you connected it to an element, the value will be that element; if you connected it to a child component, the value will be that component's component instance; if nothing is connected to it, the value will be null.
Steps
Create a ref:
const navbar = ref(null);
This will be used to refer to the element.
Connect the template ref to the element by setting the ref attribute on the element to the name you used for the template ref:
<nav ref="navbar" ...>
Paraphrasing the Template Refs docs:
ref is a special attribute. It allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.
Connect the observer when the component is mounted:
onMounted(() => {
observer.observe(navbar.value);
})
Paraphrasing the docs again:
Note that you can only access the ref after the component is mounted. If you try to access navbar before then, it will be null. This is because the element doesn't exist until after the first render!
Optionally (see below), disconnect the observer when the component is being unmounted:
onBeforeUnmount(() => {
observer.disconnect();
})
Note that I don't believe this is technically necessary, as the observer should be garbage collected when the component is destroyed.
You can fiddle around with this experiment I did in the SFC playground, trying to create a memory leak.
Code Example
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const el = ref(null);
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
observer.observe(el.value)
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div ref="el">
I'm the target!
</div>
</template>
Using querySelector
Alternatively, you can still use querySelector. The same lifecycle considerations apply.
<nav class="home-menu ...">
onMounted(() => {
const target = document.querySelector(".home-menu");
observer.observe(target);
})
Code Example
<script setup>
import { onMounted, onUnmounted } from "vue";
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
const target = document.querySelector(".target");
observer.observe(target);
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div class="target">
I'm the target!
</div>
</template>
Other Docs
This is the diagram from the Lifecycle docs:
Side Notes
The reason console.log(target) did log an object is because a Vue ref is an object. The actual value is accessed via the value property.
The technical reason for this is that Vue can then detect when that property is accessed, and do its reactivity magic; including when it was a complete reassignment of the value.

You can use the ref approach, calling the variable with the same name you declared the ref in the template:
const navbar = ref(null);
However, you should await the component to be mounted to observe:
onMounted(() => {
observer.observe(target);
})
remember also to disconnect it when you unmount the component:
onBeforeUnmount(() => {
observer.disconnect();
})

Related

What is the benefit to use useNuxtApp() on Nuxt3?

I'd like to know about new Nuxt3 feature called useNuxtApp.
Official document says, to use provide, you can do like below.
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
console.log(nuxtApp.$hello('name')) // Prints "Hello name!"
However it seems like you can also still use provide/inject.
For instance, I define the method 'hello' on parent component, then I also want to use it on child component, I can provide 'hello' for child from parent component and inject it.
You can still do same things by using provide/inject, so does anyone know what is the benefit using useNuxtApp?? And what is the difference between provide/inject and useNuxtApp except for syntax??
useNuxtApp built-in composable of Nuxt3
It is used to access shared runtime context of Nuxt available in server / client side, like: Vue App Instance, Runtime Hooks, Runtime Config Variables, Internal States etc.
Example:
i) ssrContext,
ii) payload,
iii) helper methods etc.
Values getting by this composable will be available across all composables, components, plugins (all files of .vue)
In Nuxt 2, this was referred to as "Nuxt Context"
Let's see an example how it can be use in Nuxt Plugin and by extending the plugin how it will give us a helper method and how we can use that helper method to get value to the rest of Nuxt Application.
say, we have a plugin in ~/plugins/my-plugin.js, where returning "provide" option as helper method.
in ~/plugins/my-plugin.js file
export default defineNuxtPlugin(() => {
...
return {
provide: {
hello: (msg: string) => `Hello ${msg}!`
}
}
...
})
in any template (pages, components or any .vue file of the Nuxt App)
<template>
<div>
{{ $hello('world') }}
</div>
</template>
<script setup>
const { $hello } = useNuxtApp()
</script>
// or
<script setup>
const nuxtApp = useNuxtApp()
nuxtApp.provide('hello', (name) => `Hello ${name}!`)
console.log(nuxtApp.$hello('name')) // Prints "Hello name!"
</script setup>

Next.js dynamic import with custom loading component: "Warning: Expected server HTML to contain a matching <em> in <div>."

I have a next.js page with dynamic imports, which worked well until now,
but since I started using the custom loading components I get the warning
Warning: Expected server HTML to contain a matching <em> in <div>.
What can I do to avoid this warning ?
Here a reduced example:
// pages/index.tsx:
import React from 'react';
import dynamic from 'next/dynamic';
import { LoadingComponent } from '../src/LoadingComponent';
// -- The custom loading fallback component
const loadingFallback = { loading: LoadingComponent };
// -- The dynamically loaded component
const DynamicallyLoadedComponent = dynamic(
() => import('../src/DynamicallyLoadedComponent'),
loadingFallback
);
// -- The main component
function Main(){
return <DynamicallyLoadedComponent />;
}
export default Main;
// src/DynamicallyLoadedComponent.tsx:
export default function DynamicallyLoadedComponent(){
return <div>ready loaded, final page content.</div>;
};
// src/LoadingComponent.tsx:
export const LoadingComponent = () => {
return <em>... loading ...</em>;
}
Apparently you can not extract the part { loading: LoadingComponent } into a variable, so it has to be written
inline, e.g.:
const DynamicallyLoadedComponent = dynamic(
() => import('../src/DynamicallyLoadedComponent'),
{ loading: LoadingComponent } // <-- needs to be written like this, a variable can't be used
);
Maybe this is because Next.js analyses the code in a kind of "pre-compiling" step and expects to find exactly this pattern (?).
Probably this is the same requirement as mentioned under Basic usage regarding the dynamic import itself:
Note: In import('path/to/component'), the path must be explicitly written.
It can't be a template string nor a variable.
Furthermore the import() has to be inside the dynamic() call for Next.js to be able to match
webpack bundles / module ids to the specific dynamic() call and preload them before rendering.
dynamic() can't be used inside of React rendering as it needs to be marked in the top level
of the module for preloading to work, similar to React.lazy.

Next.js getInitialProps not rendering on the index.js page

I really can't figure out what is wrong with this code on Next.js.
index.js :
import { getUsers } from "../utils/users";
import React from "react";
Home.getInitialProps = async (ctx) => {
let elements = [];
getUsers().then((res) => {
res.map((el) => {
elements.push(el.name);
});
console.log(elements);
});
return { elements: elements };
};
function Home({ elements }) {
return (
<div>
{elements.map((el, i) => {
<p key={i}>{el}</p>;
})}
</div>
);
}
export default Home;
This doesn't render anything on my main page but still console logs the right data on server side (inside the vscode console). I really can't figure out what's going on, I followed precisely the article on the next.js site.
The getUsers function is an async function that returns an array of objects (with name,surname props), in this case in the .then I'm grabbing the names and pushing them into an array that correctly logs out to the console.
How can I make this data that I get render on the page?? Surely something to do with SSR.
The problem is using async function. Try as following.
...
elements = await getUsers();
...
In your code, component is rendered before response is finished. So the data is not rendered. Suggest using "async...await...". Infact "async" and "await" are like a couple of one.

Why I don't see div in html (view page source in browser) when I use getStaticProps in next.js

I try to make test SSR app based on Next.js + React + Apollo.
But I cannot understand how SSR works.
I read the docs and I found that to get some important data while first SSR render we need use getStaticProps and the result of function call will be passed to the props of component which I try to render.
Depends on this props I can render whatever I want, for example I have next code:
import React from "react";
import {GetServerSideProps, GetStaticProps} from "next";
const Home = (props) => {
return (
<div>{props.ValFromGetStaticProps}</div>
)
};
export const getStaticProps: GetStaticProps = async (context) => {
return { props: {ValFromGetStaticProps: 'ValFromGetStaticProps!'} }
};
export default Home;
I expect to see rendered this code on the server side, and if I open sources of the HTML I should see this div. But instead I see just some props object... And there is no div ((
Remark: in the DOM this div present, but for SEO purpose this div should exists in the page source.
Ohh... I didn't see it because I blocked first render early in the parent component:
_app.tsx
export default function ({ Component, pageProps }) {
<BackendDataProvider>
<Component {...pageProps}>
</BackendDataProvider>
}
in the BackendDataProvider was condition
const {data, error, loading} = useQuery(BACKEND_QUERY)
if (error || loading) {
return null;
}
so that was a reason why first render was not rendered correctly

Redux Connect: mapDispatchToProps pass in the result of an action creator

I'm learning redux and and wanted to know how dispatch passes in result of an action creator as stated in the screenshot below taken from redux doc.
The learning app code is available here: https://github.com/ZhangMYihua/lesson-12/tree/master/src
As per the course instructor the below mapDispatchToProps code updates the header component state using user.action.js. I'm not able to understand how all this works even after reading the redux documentation.https://react-redux.js.org/using-react-redux/connect-mapdispatch
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
The initial state of the currentUser in the header component is set using the user.reducer.js file in the redux folder. This part is clear.
When we sign in using Google SignIn, the createUserProfileDocument(userAuth) function in App.js will check if the google login is available in the firestore users collections. If not it will create a copy of the account with the required fields. The user login details are fetched from the firestore users collections which are passed in the userRef in App.js.
What i do not understand is how data from userRef in componentDidMount() gets passed to header component using dispatch when we login using google signin?
componentDidMount() {
const {setCurrentUser} = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
});
}
Below is the example from the react-redux documentation which is similar to the above.
This part in your componentDidMount is passing userRef data to the redux store:
const {setCurrentUser} = this.props;
// ...
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
mapDispatchToProps
The code calls this.props.setCurrentUser and that is function which you define in your mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
So this.props.setCurrentUser function definition is simple like this (where dispatch is defined in upper scope):
this.props.setCurrentUser = user => dispatch(setCurrentUser(user))
The function takes a single parameter user and calls dispatch(setCurrentUser(user)). Here the setCurrentUser(user) is your action creator that produce some action (something like this {action: 'SET_CURRENT_USER', payload: {user}}). This action is dispatched to redux store with dispatch function (the react-redux passes correct dispatch function to your mapDispatchToProps through react-redux connect, see bellow).
connect
The mapDispatchToProps is useless if you are not use it as an argument of the react-redux connect function. You usually wrap your component with connect and export the wrapped component not just the component itself. So whenever you import your App.js module and use the default export from it as component in some JSX tree, it is not just your component, but it is your component wrapped with react-redux magic. This magic ensures, that the mapDispatchToProps is called with correct dispatch argument (store.dispatch from redux library) and enhances your component props object with another properties, the ones form mapStateToProps and the ones from mapDispatchToProps.
// here you are wrapping your App component with `connect` from `react-redux`
export default connect(
null,
mapDispatchToProps
)(App);
In next file you will import and use the wrapped component like this:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App'; // here you are importing the wrapped component
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
It is necessary to have your components which uses react-redux have wrapped with Provider in the app component tree. Otherwise connect will not have access to redux store so connecting component to redux store won't work.
EDIT
mapStateToProps
this.props.setCurrentUser is used to dispatching action to your redux store which just MODIFIES it (how the store is modified is up to your implementation of reducer - see user.reducer.js in your example).
If you want to access some part of your redux store, you need to pass the mapStateToProps by the first argument (see above that in example of your App component the first argument of connect was null).
You can imagine the redux store as a single javascript object. The first definition of this object you can see in your root-reducer.js:
export default combineReducers({
user: userReducer
});
This defines that your redux store (object) has the property user on the top level. The value of property user handles user.reducer.js. That reducer defines initial value of the user part of the store and also defines how it can be modified (by redux actions). So according to your example your initial store is this:
{
user: {
currentUser: null
}
}
If you want access it in your component (props) you need to use mapStateToProps as I stated above, like this (see the file header.component.jsx in your example):
// header.component.jsx
const mapStateToProps = state => ({ // react-redux passes here the whole redux store to the argument state
/*
you want access currentUser of user reducer,
so you just access it the same way as you would do it
in javascript object, with use of dots.
*/
currentUser: state.user.currentUser //
});
export default connect(mapStateToProps)(Header);
And then you access the connected store value in your component by props:
// whenever in your component code
console.log(this.props.currentUser);

Resources