How to use `useRoute`/`useRouter` in a Pinia Store using Setup Store syntax in Vue3? - vuejs3

I've been trying to get my Pinia store up and running in Vue 3 and it all has been pretty effortless until I wanted to access some parameters in the url.
I have a store (simplified) like so:
import { defineStore } from 'pinia';
import { useRoute } from 'vue-router';
import { useLocalStorage } from '#vueuse/core';
export const useUserStore = defineStore('user', () => {
const route = useRoute();
const uuid = ref(
useLocalStorage('uuid', route.params.id)
)
return { uuid };
})
Unfortunately, the route remains undefined as if useRoute() is not triggered properly. I've seen that you can add plugins to add the router instance to the pinia store on initialisation, but there's no way I can find to access that this instance in a Setup Store.
Any help would be greatly appreciated

route is not defined when the pinia is initiated.
You need to wait a bit.
One way to do this is to call the function when the component is loaded.
export const useUserStore = defineStore('user', () => {
const route = useRoute();
const id = ref('');
const setId = () => {
id.value = route.params.id as string; // don't need as string if you don't use TypeScript
};
return { id, setId };
});
<script setup lang="ts">
import { useUserStore } from '../stores/user';
const user = useUserStore();
user.setId(); // call the function from pinia, route.params works just fine
</script>
Link Demo

Related

React Query - useQuery callback dependent on route parameter? [duplicate]

When page is refreshed query is lost, disappears from react-query-devtools.
Before Next.js, I was using a react and react-router where I would pull a parameter from the router like this:
const { id } = useParams();
It worked then. With the help of the, Next.js Routing documentation
I have replaced useParams with:
import { usePZDetailData } from "../../hooks/usePZData";
import { useRouter } from "next/router";
const PZDetail = () => {
const router = useRouter();
const { id } = router.query;
const { } = usePZDetailData(id);
return <></>;
};
export default PZDetail;
Does not work on refresh. I found a similar topic, but manually using 'refetch' from react-query in useEffects doesn't seem like a good solution. How to do it then?
Edit
Referring to the comment, I am enclosing the rest of the code, the react-query hook. Together with the one already placed above, it forms a whole.
const fetchPZDetailData = (id) => {
return axiosInstance.get(`documents/pzs/${id}`);
};
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {});
};
Edit 2
I attach PZList page code with <Link> implementation
import Link from "next/link";
import React from "react";
import TableModel from "../../components/TableModel";
import { usePZSData } from "../../hooks/usePZData";
import { createColumnHelper } from "#tanstack/react-table";
type PZProps = {
id: number;
title: string;
entry_into_storage_date: string;
};
const index = () => {
const { data: PZS, isLoading } = usePZSData();
const columnHelper = createColumnHelper<PZProps>();
const columns = [
columnHelper.accessor("title", {
cell: (info) => (
<span>
<Link
href={`/pzs/${info.row.original.id}`}
>{`Dokument ${info.row.original.id}`}</Link>
</span>
),
header: "Tytuł",
}),
columnHelper.accessor("entry_into_storage_date", {
header: "Data wprowadzenia na stan ",
}),
];
return (
<div>
{isLoading ? (
"loading "
) : (
<TableModel data={PZS?.data} columns={columns} />
)}
</div>
);
};
export default index;
What you're experiencing is due to the Next.js' Automatic Static Optimization.
If getServerSideProps or getInitialProps is present in a page, Next.js
will switch to render the page on-demand, per-request (meaning
Server-Side Rendering).
If the above is not the case, Next.js will statically optimize your
page automatically by prerendering the page to static HTML.
During prerendering, the router's query object will be empty since we
do not have query information to provide during this phase. After
hydration, Next.js will trigger an update to your application to
provide the route parameters in the query object.
Since your page doesn't have getServerSideProps or getInitialProps, Next.js statically optimizes it automatically by prerendering it to static HTML. During this process the query string is an empty object, meaning in the first render router.query.id will be undefined. The query string value is only updated after hydration, triggering another render.
In your case, you can work around this by disabling the query if id is undefined. You can do so by passing the enabled option to the useQuery call.
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {
enabled: id
});
};
This will prevent making the request to the API if id is not defined during first render, and will make the request once its value is known after hydration.

Using the context API in Next.js

I'm building a simple Next.js website that consumes the spacex graphql API, using apollo as a client. I'm trying to make an api call, save the returned data to state and then set that state as context.
Before I save the data to state however, I wanted to check that my context provider was actually providing context to the app, so I simply passed the string 'test' as context.
However, up[on trying to extract this context in antoher component, I got the following error:
Error: The default export is not a React Component in page: "/"
My project is set up as follows, and I'm thinking I may have put the context file in the wrong place:
pages
-api
-items
-_app.js
-index.js
public
styles
next.config.js
spacexContext.js
Here's the rest of my app:
spaceContext.js
import { useState,useEffect,createContext } from 'react'
import { ApolloClient, InMemoryCache, gql } from "#apollo/client"
export const LaunchContext = createContext()
export const getStaticProps = async () => {
const client = new ApolloClient({
uri: 'https://api.spacex.land/graphql/',
cache: new InMemoryCache()
})
const { data } = await client.query({
query: gql`
query GetLaunches {
launchesPast(limit: 10) {
id
mission_name
launch_date_local
launch_site {
site_name_long
}
links {
article_link
video_link
mission_patch
}
rocket {
rocket_name
}
}
}
`
});
return {
props: {
launches: data.launchesPast
}
}
}
const LaunchContextProvider = (props) => {
return(
<LaunchContext.Provider value = 'test'>
{props.children}
</LaunchContext.Provider>
)
}
export default LaunchContextProvider
_app.js
import LaunchContextProvider from '../spacexContext'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return (
<LaunchContextProvider>
<Component {...pageProps} />
</LaunchContextProvider>
)
}
export default MyApp
Any suggestions on why this error is appearing and how to fix it?

Correct way of reusing functions in Composition API

I use Vue3 Composition API and am trying to explore its reusability possibilities. But I feel that I don't understand how it should be used.
For example, I extracted the login function to a file, to use it on login, and also after registration.
#/services/authorization:
import { useRoute, useRouter } from "vue-router";
import { useStore } from "#/store";
import { notify } from "#/services/notify";
const router = useRouter(); // undefined
const route = useRoute(); // undefined
const store = useStore(); // good, but there is no provide/inject here.
export async function login(credentials: Credentials) {
store
.dispatch("login", credentials)
.then(_result => {
const redirectUrl =
(route.query.redirect as string | undefined) || "users";
router.push(redirectUrl);
})
.catch(error => {
console.error(error);
notify.error(error.response.data.message);
});
}
interface Credentials {
email: string;
password: string;
}
#/views/Login:
import { defineComponent, reactive } from "vue";
import { useI18n } from "#/i18n";
import { login } from "#/services/authorization";
export default defineComponent({
setup() {
const i18n = useI18n();
const credentials = reactive({
email: null,
password: null
});
return { credentials, login, i18n };
}
});
And the problem is that route and router are both undefined, because they use provide/inject, which can be called only during setup() method. I understand why this is happening, but I don't get what is correct way to do this.
Currently, I use this workaround #/services/authorization:
let router;
let route;
export function init() {
if (!router) router = useRouter();
if (!route) route = useRoute();
}
And in Login (and also Register) component's setup() i call init(). But I feel that it's not how it's supposed to work.

Vue Reactivity: Creating reactive data with provide and inject

My aim is to validate a users' credentials from vue frontend, fetch their data, store the data in a globally available variable and use them in any component. After some work, I'm able to use Vue's reactive() method with inject and provide. Here's my current code:
In store/index.js
import { reactive, inject } from 'vue'
// global states
export const stateSymbol = Symbol('state')
export const createState = () => reactive({
backendRoute: 'http://127.0.0.1:8000/',
currentUser: {},
logIn: userData => {
let user = createState.currentUser
user = userData
console.log(user)
}
})
export const useState = () => inject(stateSymbol)
Then in main.js
import { createApp } from 'vue'
import App from './App.vue'
// store modules
import { stateSymbol, createState } from './store'
// create app
const app = createApp(App)
app.provide(stateSymbol, createState())
Then in login/register component named Login.vue
import { useState } from "../store"
import axios from 'axios'
export default {
name: 'RegisterLogin',
setup() {
let state = useState()
axios.get(getUser, jwtConfig).then(response => {
userId = response.data.id
const currentUser = getUserProfile+userId // URL
console.log(currentUser)
const getCurrentUser = axios.get(currentUser, jwtConfig)
getCurrentUser.then(response => {
state.logIn(response.data)
console.log(state.currentUser)
})
})
}
}
console.log(user) in store/index.js logs the userData as expected. I believe with that, createState.currentUser should be mutated. The problem however is console.log(state.currentUser) logs an empty proxy object. Also, if I try accessing the state.currentUser from another component like Home.vue:
<template>
{{ state.currentUser }}
</template>
<script>
import { useState } from '../store/'
export default {
name: 'Index',
setup() {
return {
state: useState(),
}
},
}
</script>
...an empty object is always displayed even after the user logs in successfully. Is there anything I'm doing wrong? Thank you for your assistance (in advance).
I was looking through the code but couldn't find anywhere where the variable was getting mutated, but I believe I know what's going on.
You are reassigning the user object, not assigning the createState.currentUser to another object.
import { reactive, inject } from 'vue'
// global states
export const stateSymbol = Symbol('state')
export const createState = reactive({
backendRoute: 'http://127.0.0.1:8000/',
currentUser: {},
logIn: userData => {
// over here you assign user to createState.currentUser
let user = createState.currentUser;
// then you reassign user to `user data`
user = userData
console.log(user)
}
})
export const useState = () => inject(stateSymbol)
you could instead try
import { reactive, inject } from 'vue'
// global states
export const stateSymbol = Symbol('state')
export const createState = () => reactive({
backendRoute: 'http://127.0.0.1:8000/',
currentUser: {},
logIn: userData => {
createState.currentUser = userData
console.log(createState.currentUser)
}
})
export const useState = () => inject(stateSymbol)
As an aside, why are you using inject? I can't figure out what you're expecting it to do.
export const useState = () => stateSymbol would have the same result, but you don't even need to pass the function, you can just use something more concise like...
//////// store
import { reactive } from 'vue'
// global states
export const store = reactive({
backendRoute: 'http://127.0.0.1:8000/',
currentUser: {}
})
export const logIn = (userData) => {
store.currentUser = userData
}
////////////////////////////////////////////////
////// main
import { store, logIn } from "../store"
import axios from 'axios'
export default {
name: 'RegisterLogin',
setup() {
axios.get(getUser, jwtConfig).then(response => {
userId = response.data.id
const currentUser = getUserProfile+userId // URL
console.log(currentUser)
const getCurrentUser = axios.get(currentUser, jwtConfig)
getCurrentUser.then(response => {
logIn(response.data)
console.log(store.currentUser)
})
})
}
}
It appears #Daniel was right after all. My logIn method in store/index.js was not really mutating the currentUser property. In fact, I'm unable to access that property from the method. To solve this, I had to do the mutation immediately after the axios call like state.currentUser = response.data in then method and boom, the state is updated in all components! Thanks.

redux state not changing

I'm using a Net Core, React-Redux boiler-plate, and when I run the fetch api action, the reducer state does not change at all.
Here is my action
import axios from "axios";
import config from '../config';
const ROOT_URL = config[process.env.NODE_ENV].api;
export const FETCH_EVENTS = "FETCH_EVENTS";
export function fetchEvents() {
const url = ROOT_URL +"/Event/GetAllEvents";
const request = axios.get(url);
return {
type: FETCH_EVENTS,
payload: request
};
}
my index reducer:
import { combineReducers} from 'redux';
import { routerReducer } from 'react-router-redux';
import dataReducer from './dataReducer'
const reducers = {
events: dataReducer
};
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
export default rootReducer;
and my reducer:
import { FETCH_EVENTS } from "../actions/ExtractActions";
export default function (state = [], action) {
switch (action.type) {
case FETCH_EVENTS:
console.log("inside reducer")
return [action.payload, ...state];
}
return state;
}
So I add this code in the Home component:
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchEvents }, dispatch);
}
function mapStateToProps(state) {
return {
events: state.events
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
but when I try to run the action and try to see if the reducer state has changed, I get on console log an empty array for "this.props.events". Even though if I am trying to store api data to the state, I even tried modifying the reducer method and simply returning a string, but this.props.events returns an empty array [] again. I am guessing my redux is not working but I don't know why. I've been debugging all night long
componentWillMount() {
this.props.fetchEvents()
console.log(this.props.events)
}
I found the error. For some reason I had to call this.props.events in the render() method and not componentwillmount.
axios.get() is an async function. That's why you couldn't see the updated state when you logged it right after fetching the events. I would recommend you to use the redux-devtools-extension for debugging. Hope this helps. Cheers!

Resources