Access current route inside a getter in Pinia? - vuejs3

I would like to know what is the current route so that I can compute the size of the sidebar.
import { defineStore } from 'pinia'
export const useSidebarStore = defineStore('sidebar', {
state: () => {
return {
full: true // can be toggled by clicking on the sidebar toggle button
}
},
getters: {
// TODO secondarySidebar is open if current route is settings
// secondarySidebarOpen: (state) =>
// TODO create a getter that returns the current route
currentRoute (state, getters, rootState, rootGetters) {
return
}
},
actions: {
}
})
export default useSidebarStore
Can you please help?

A solution I found is to store the current route to the state of the store by using beforeEach method of the router.
import { createRouter, createWebHistory } from 'vue-router'
import routes from '#/router/routes.js'
import { useSidebarStore } from '#/stores/sidebar.js'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
router.beforeEach(async (to) => {
const sidebarStore = useSidebarStore()
sidebarStore.currentRoutePath = to.path
return true
})
export default router

Related

Next.js: Correct way to get dynamic route param when change triggered by Next.Link

I have a kanban app build with Next.js. I currently have two boards:
{"name": "New Board", "id": "6db0ceec-d371-4b53-8065-2eeebac4694a"}
{"name": "tired": "cc41d33e-43a1-49bd-8b76-18e46417b27a"}
I have a menu which maps over next Link, rendering links like so:
<Link href={`/board/${board.id}`}>{board.name}</Link>
I then have the following:
src/pages/board/[boardId].js (page)
src/pages/api/board/[boardId].js (API end point)
In the page, I've defined an async function which sends a GET request to the end point that retrieves the data. For SSR, it's called in getServerSideProps() (this would be called when a user navigates to a specific board page from another part of the app). For client-side, I call this in an effect. (This is called when the user is already on the board page but they select a different board from the menu).
The issue I am having is figuring out the correct Next.js idiomatic way to get the new id from the route when it is changed. I've tried using router.query and router.asPath. However, it often gives me the old value (before the route changed). The only way I am reliably able to get the correct param when the route changes is to use window.location.pathname.split('/')[2].
I will include the source code for the page as well as some console.log() output which will show how the three methods of getting the id from the route are inconsistent (window is always correct) as I switch back and forth between the two boards by clicking the Links in the menu:
// src/pages/board/[boardId].js
import React, { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import supabase from 'Utilities/SupabaseClient'
import Board from 'Components/Screens/Board/Board'
import { useRouter } from 'next/router'
import axios from 'axios'
import { getBaseUrl } from 'Utilities'
import { hydrateTasks } from 'Redux/Reducers/TaskSlice'
const BoardPage = (props) => {
const router = useRouter()
const dispatch = useDispatch()
async function handleRouteChange() {
const { asPath } = router
const { boardId } = router.query // sometimes this does not update!
const idFromWindow = window.location.pathname.split('/')[2]
const { board, tasks } = await handleFetchData({boardId: idFromWindow})
console.log(`hello from handleRouteChange:\n\nFrom window: ${idFromWindow}\n\nFrom router.query: ${boardId}\n\nFrom router.asPath: ${asPath}`)
dispatch(hydrateTasks({board, tasks}))
}
useEffect(() => {
//subscribe
router.events.on('routeChangeComplete', handleRouteChange);
//unsubscribe
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [ router.events]);
return (
<Board {...props}/>
)
}
const handleFetchData = async ({boardId, req}) => {
const baseUrl = getBaseUrl(req)
return axios.get(`${baseUrl}/api/board/${boardId}`)
.then(({data}) => data)
.catch(err => { console.log(err)})
}
export async function getServerSideProps ({ query, req }) {
const { user } = await supabase.auth.api.getUserByCookie(req)
if (!user) {
return { props: {}, redirect: { destination: '/signin' } }
}
const { boardId } = query
const { board, tasks} = await handleFetchData({boardId, req})
return { props: { user, board, tasks } }
}
export default BoardPage
Starting from the "tired" board, I click back and forth between "New Board" and "tired". Observe the console output. The window is always correct. The router is frequently wrong:
// click 1
[boardId].js?0a51:19 hello from handleRouteChange:
From window: 6db0ceec-d371-4b53-8065-2eeebac4694a
From router.query: cc41d33e-43a1-49bd-8b76-18e46417b27a
From router.asPath: /board/cc41d33e-43a1-49bd-8b76-18e46417b27a
// click 2
[boardId].js?0a51:19 hello from handleRouteChange:
From window: cc41d33e-43a1-49bd-8b76-18e46417b27a
From router.query: cc41d33e-43a1-49bd-8b76-18e46417b27a
From router.asPath: /board/cc41d33e-43a1-49bd-8b76-18e46417b27a
// click 3
[boardId].js?0a51:19 hello from handleRouteChange:
From window: 6db0ceec-d371-4b53-8065-2eeebac4694a
From router.query: cc41d33e-43a1-49bd-8b76-18e46417b27a
From router.asPath: /board/cc41d33e-43a1-49bd-8b76-18e46417b27a
// click 4
[boardId].js?0a51:19 hello from handleRouteChange:
From window: cc41d33e-43a1-49bd-8b76-18e46417b27a
From router.query: cc41d33e-43a1-49bd-8b76-18e46417b27a
From router.asPath: /board/cc41d33e-43a1-49bd-8b76-18e46417b27a
I'm new to Next.js, so it's possible I am going about this the wrong way...
How I have done this is -
Suppose I have a page called localhost:3000/board
I have done this with state, and not with [boardId] (lets called this state as boardId and initialvalue be null)
Suppose a user from anywhere in the app visit this page, using the Link
<Link href="/board">
Go To Board
</Link>
on the page mount I try to read the value of boardId from url such as -
useEffect(() => {
if (router.query && router.query.boardId )
{
setBoardId(router.query.boardId);
}
}, []);
and if fount I set the state of boardId, also I do this to get the data from API
useEffect(() => {
if (boardId) getBoardIdDataFromApi();
}, [boardId] );
In the above Case the board Id will be null as I'm not passing any Id as params to the url. (In my case I create a new board here)
Case 2 - suppose a User visit this board page with something like this, from anywhere in the page -
<Link
href={{
pathname: "/board",
query: { boardId: boardId },
}}
>
this time url will be like
localhost:3000?boardId=AnyBoardId
and this will load the Id and get actual data from the api, or change the layout accodringingly.
useEffect(() => {
if (router.query && router.query.boardId )
{
setBoardId(router.query.boardId);
}
}, []);
Case - 3
Now when a user change the boaardId fromt being on the page itself, you can do -
const onChangeBoard = (v) => {
router.push('/board?boardId=${v}', undefined, { shallow: true })
setboardId(v);
}
This will upadte the state of boardId and fetch the data once the user chooses a different board and update the url.
I'm experimenting with {shallow:true}, and I have all the data fetching mechanisms on the client side.
For you -
you can block getServerSideProps for Case 1
Use getServerSideProps for case 2
For case 3, if you remove shallow, you can again use getServerSideProps but please verify.
This may not be the exact answer. but can help you to understand the logic
Okay I got this working by checking the effect to:
useEffect(() => {
async function handleRouteChange() {
const { boardId } = router.query
const { board, tasks } = await handleFetchData({ boardId })
dispatch(hydrateTasks({ board, tasks }))
}
handleRouteChange()
}, [router])
Here is the complete code for the page now:
// src/pages/board/[boardId].js
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import supabase from 'Utilities/SupabaseClient'
import Board from 'Components/Screens/Board/Board'
import { useRouter } from 'next/router'
import axios from 'axios'
import { getBaseUrl } from 'Utilities'
import { hydrateTasks } from 'Redux/Reducers/TaskSlice'
const BoardPage = (props) => {
const router = useRouter()
const dispatch = useDispatch()
useEffect(() => {
async function handleRouteChange() {
const { boardId } = router.query
const { board, tasks } = await handleFetchData({ boardId })
dispatch(hydrateTasks({ board, tasks }))
}
handleRouteChange()
}, [router])
return (
<Board {...props}/>
)
}
const handleFetchData = async ({boardId, req}) => {
const baseUrl = getBaseUrl(req)
return axios.get(`${baseUrl}/api/board/${boardId}`)
.then(({data}) => data)
.catch(err => { console.log(err)})
}
export async function getServerSideProps ({ query, req }) {
const { user } = await supabase.auth.api.getUserByCookie(req)
if (!user) {
return { props: {}, redirect: { destination: '/signin' } }
}
const { boardId } = query
const { board, tasks} = await handleFetchData({boardId, req})
return { props: { user, board, tasks } }
}
export default BoardPage

Next/Router emit events

I'm writing a custom hook to prevent a user from navigating away if there's unsaved information (in a form) on a page.
To do this, I want to emit a router event when a route change has been cancelled. However, router.events.emit('routeChangeStart') causes a a type error TypeError: Cannot read properties of undefined (reading 'shallow')
import { useRouter } from 'next/router'
import { useEffect, useCallback } from 'react'
export default function usePreventWindowUnload(preventDefault: boolean) {
const confirmMessage = 'Are you sure?'
const router = useRouter()
const onRouterChangeStart = useCallback(() => {
if (preventDefault) {
if (window.confirm(confirmMessage)) {
return true
}
}
// line causing error
router.events.emit('routeChangeError')
throw 'cancelled route change'
,} [preventDefault])
useEffect(() => {
router.events.on('routeChangeStart', onRouteChangeStart)
return () => {
router.events.off('routeChangeStart', onRouteChangeStart)
}
}, [preventDefault])
}
How do I fix this?

Redux Thunk not dispatching

I have installed Redux Thunk on my application and it's been working fine so far, all of the previous actions I've created are pulling out data from APIs successfully, however the following action is not even dispatching actions to my reducer, any idea what am I missing?
// my action
export const fetchClub = id => {
debugger
return (dispatch) => {
if (id){
dispatch ({type: 'START_PULLING_NIGHTCLUB'});
let targetUrl = `http://localhost:3001/nightclub`
fetch(targetUrl)
.then(res => {
debugger
return res.json()
})
.then(nightclub => dispatch({type: 'CURRENT_NIGHTCLUB', nightclubs: nightclub.result}))
.catch(error => {
console.log(error)
})
}}}
//my reducer
import {combineReducers} from "redux"
const rootReducer = combineReducers({
nightclubs: nightClubsReducer,
user: userReducer
})
export default rootReducer
function nightClubsReducer(state = {}, action) {
debugger
switch (action.type){
case 'ADD_NIGHTCLUBS':
debugger
let nightclubs = action.nightclubs
// filering the results just to show nightclubs rather than hotels
nightclubs = nightclubs.filter( function (nightclub){
return !nightclub.types.includes("lodging")
})
return {...state.nightclubs, nightclubs}
case 'CURRENT_NIGHTCLUB':
debugger
let nightclub = action.nightclub
return {...state.nightclubs, nightclub}
default:
return state
}}
function userReducer(state = {user: {logged_in: false}}, action){
let current_user = {}
switch (action.type){
case 'ADD_USER_LOCATION':
let coords = action.location.coords
return {...state.user, coords}
case 'CREATE_USER':
current_user = action.user
state.logged_in = true
return {...state.user, current_user}
case 'ADD_LOGGED_IN_USER':
current_user = action.user
if(state.user){
state.user.logged_in = action.user.logged_in}
return {...state.user, current_user}
default:
return state
}
}
I should be hitting the debugger on the first line of my nightClubsReducer however nothing happens.
My Nightclub component is connected properly as far as I'm aware:
import React, { Component } from 'react';
import Maya from '../assets/Mayaclubbio.jpg'
import '../NightClubPage.css'
import { connect } from 'react-redux';
import { fetchClub } from '../actions/NightClubs';
class NightClub extends Component {
constructor(props) {
super(props);
this.id = props.match.params.id
}
componentDidMount() {
fetchClub(this.id)
}
render() {
debugger
return (
<React.Fragment>
//HTML code
</React.Fragment>
)
}
}
const mapStateToProps = (state) => {
debugger
return {
nightclub: state.nightclubs.nightclub,
user: state.user
}
}
export default connect(mapStateToProps, { fetchClub })(NightClub);
I have no clue what could be failing as I'm using the same logic for the rest of my actions and they are working just fine.
I think calling the action from props should fix your issue
componentDidMount() {
this.props.fetchClub(this.id);
}

Redux combineReducer returns default state for reducer not called in action

I'm new to react redux, so I think I'm just missing something basic.
I have three reducers, two to handle orders that update in the store as arrays, and one that shows the status of a web socket connection I'm using to receive orders from the server.
// reducers.js
import { combineReducers } from 'redux'
import { ADD_POS_ORDER, ADD_MOBILE_ORDER, UPDATE_WS_STATUS, wsStatuses } from '../actions/actions'
const { UNINITIALIZED } = wsStatuses
const posOrders = (state = [], action) => {
switch (action.type) {
case ADD_POS_ORDER:
return [
...state,
{
id: action.order.id,
status: action.order.status,
name: action.order.name,
pickupNum: action.order.pickupNum
}
]
default:
return state
}
}
const mobileOrders = (state = [], action) => {
switch (action.type) {
case ADD_MOBILE_ORDER:
return [
...state,
{
id: action.order.id,
status: action.order.status,
name: action.order.name,
pickupNum: action.order.pickupNum
}
]
default:
return state
}
}
const wsStatus = (state = UNINITIALIZED, action) => {
switch (action.type) {
case UPDATE_WS_STATUS:
return action.status
default:
return state
}
}
const displayApp = combineReducers({
posOrders,
mobileOrders,
wsStatus
})
export default displayApp
When I connect to the socket, I dispatch an action to update wsStatus and the action is stored as 'CONNECTED'.
When I follow with an order with the posOrders reducer, the wsStatus is reset to its default, 'UNINITIALIZED'.
What I am struggling to understand is why wsStatus is not using the previous state of 'CONNECTED', but instead returning default.
// actions.js
export const UPDATE_WS_STATUS = 'UPDATE_WS_STATUS'
export const wsStatuses = {
UNINITIALIZED: 'UNINITIALIZED',
CONNECTING: 'CONNECTING',
CONNECTED: 'CONNECTED',
DISCONNECTED: 'DISCONNECTED'
}
export const ADD_POS_ORDER = 'ADD_POS_ORDER'
export const ADD_MOBILE_ORDER = 'ADD_MOBILE_ORDER'
export const UPDATE_POS_ORDER = 'UPDATE_POS_ORDER'
export const setWsStatus = (status) => {
return {
type: 'UPDATE_WS_STATUS',
status: status
}
}
export const updateOrderQueue = (action, order) => {
return {
type: action,
id: order.id,
order: order,
receivedAt: Date.now()
}
}
Here's where I make the calls:
// socketListeners.js
import { setWsStatus } from '../actions/actions'
import SockJS from 'sockjs-client'
export const socket = new SockJS('http://localhost:3000/echo')
export default function (dispatch, setState) {
socket.onopen = function () {
dispatch(setWsStatus('CONNECTED'))
}
socket.onclose = function () {
dispatch(setWsStatus('DISCONNECTED'))
}
}
// orders container
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { socket } from '../helpers/socketListeners'
import { updateOrderQueue, setWsStatus } from '../actions/actions'
import PosOrder from '../components/queue/PosOrder'
class PosOrderList extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
const { dispatch } = this.props
socket.onmessage = function(e) {
// convert order info to object
let parsedOrder = JSON.parse(e.data)
let action = parsedOrder.action
let order = parsedOrder.order
dispatch(updateOrderQueue(action, order))
}
}
render() {
const { updateOrderQueue } = this.props
return (
<ul>
{this.props.posOrders.map(posOrder =>
<PosOrder
key={posOrder.id}
{...posOrder}
/>
)}
</ul>
)
}
}
PosOrderList.propTypes = {
posOrders: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.hash,
status: PropTypes.string,
name: PropTypes.string,
pickupNum: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
}))
}
// send data to component props
const mapStateToProps = (state) => {
return {
posOrders: state.posOrders,
}
}
export default connect(mapStateToProps)(PosOrderList)
// store
const store = configureStore(initialState)
export default function configureStore(initialState) {
return createStore(
displayApp,
initialState,
applyMiddleware(
createLogger({
stateTransformer: state => state.toJS()
}),
thunk,
// socketMiddleware
)
)
}
addSocketListeners(store.dispatch, store.getState)
Lastly, the store logs here: redux store
Any and all help on this would be very appreciated! Thank you!
When you compose your reducer with combineReducers, for each dispatched action, all subreducers get invoked, since every reducer gets a chance to respond to every action.
Therefore, all state gets initialized after the first action is dispatched.
Your reducers are working fine https://jsfiddle.net/on8v2z8j/1/
var store = Redux.createStore(displayApp);
store.subscribe(render);
store.dispatch({type: 'UPDATE_WS_STATUS',status:'CONNECTED'});
store.dispatch({type: 'ADD_POS_ORDER',id:'id'});
store.dispatch({type: 'UPDATE_WS_STATUS',status:'DISCONNECTED'});

Why reducer is not being activated after dispatch()

When I submit my SignIn Form validateAndSignInUser is called and this dispatch signInUser that sends to backend an email and password to get a session token. That works right.
After signInUser returns values signInUserSuccess is dispatched, and I verified this works.
But after that the UserReducer is not beign activated and I don't understand why. What is wrong?
I have this action in my SignInFormContainer.js:
import { reduxForm } from 'redux-form';
import SignInForm from '../components/SignInForm';
import { signInUser, signInUserSuccess, signInUserFailure } from '../actions/UsersActions';
const validateAndSignInUser = (values, dispatch) => {
return new Promise ((resolve, reject) => {
let response = dispatch(signInUser(values));
response.payload.then((payload) => {
// if any one of these exist, then there is a field error
if(payload.status != 200) {
// let other components know of error by updating the redux` state
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
} else {
// store JWT Token to browser session storage
// If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
// sessionStorage = persisted only in current tab
sessionStorage.setItem('dhfUserToken', payload.data.token);
// let other components know that we got user and things are fine by updating the redux` state
dispatch(signInUserSuccess(payload));
resolve(); // this is for redux-form itself
}
}).catch((payload) => {
// let other components know of error by updating the redux` state
sessionStorage.removeItem('dhfUserToken');
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
});
});
}
const mapDispatchToProps = (dispatch) => {
return {
signInUser: validateAndSignInUser /*,
resetMe: () => {
// sign up is not reused, so we dont need to resetUserFields
// in our case, it will remove authenticated users
// dispatch(resetUserFields());
}*/
}
}
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
// connect: first argument is mapStateToProps, 2nd is mapDispatchToProps
// reduxForm: 1st is form config, 2nd is mapStateToProps, 3rd is mapDispatchToProps
export default reduxForm({
form: 'SignInForm',
fields: ['email', 'password'],
null,
null,
validate
}, mapStateToProps, mapDispatchToProps)(SignInForm);
This three actions in UserActions.js
import axios from 'axios';
//sign in user
export const SIGNIN_USER = 'SIGNIN_USER';
export const SIGNIN_USER_SUCCESS = 'SIGNIN_USER_SUCCESS';
export const SIGNIN_USER_FAILURE = 'SIGNIN_USER_FAILURE';
export function signInUser(formValues) {
const request = axios.post('/login', formValues);
return {
type: SIGNIN_USER,
payload: request
};
}
export function signInUserSuccess(user) {
console.log('signInUserSuccess()');
console.log(user);
return {
type: SIGNIN_USER_SUCCESS,
payload: user
}
}
export function signInUserFailure(error) {
console.log('signInUserFailure()');
console.log(error);
return {
type: SIGNIN_USER_FAILURE,
payload: error
}
}
And this is my reducer UserReducer.js
import {
SIGNIN_USER, SIGNIN_USER_SUCCESS, SIGNIN_USER_FAILURE,
} from '../actions/UsersActions';
const INITIAL_STATE = {user: null, status:null, error:null, loading: false};
export default function(state = INITIAL_STATE, action) {
let error;
switch(action.type) {
case SIGNIN_USER:// sign in user, set loading = true and status = signin
return { ...state, user: null, status:'signin', error:null, loading: true};
case SIGNIN_USER_SUCCESS://return authenticated user, make loading = false and status = authenticated
return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
case SIGNIN_USER_FAILURE:// return error and make loading = false
error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors
return { ...state, user: null, status:'signin', error:error, loading: false};
default:
return state;
}
}
Reducers combined:
import { combineReducers } from 'redux';
import { UserReducer } from './UserReducer';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
user: UserReducer,
form: formReducer // <-- redux-form
});
export default rootReducer;
configureStore.js
import {createStore, applyMiddleware, combineReducers, compose} from 'redux';
import thunkMiddleware from 'redux-thunk';
import {devTools, persistState} from 'redux-devtools';
import rootReducer from '../reducers/index';
let createStoreWithMiddleware;
// Configure the dev tools when in DEV mode
if (__DEV__) {
createStoreWithMiddleware = compose(
applyMiddleware(thunkMiddleware),
devTools(),
persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(createStore);
} else {
createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
}
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import configureStore from './store/configureStore';
import {renderDevTools} from './utils/devTools';
const store = configureStore();
ReactDOM.render(
<div>
{/* <Home /> is your app entry point */}
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
{/* only renders when running in DEV mode */
renderDevTools(store)
}
</div>
, document.getElementById('main'));

Resources