Not able to fetch store values when a link is hit directly - plugins

Hitting a link from a website without visiting the Homepage is not setting the values in the store.
I have made axios calls in nuxtServerInit action and have set the data in mutations. I am able to access this data everywhere in the project now but if I load a particular page from the website then the data is not shown until refreshed.
Following is store/index.js file
/* eslint-disable */
import Vuex from 'vuex'
import axios from 'axios'
const createStore = () => {
return new Vuex.Store({
state: {
organisation: Object,
},
mutations: {
setOrganisation(state, organisation){
state.organisation = organisation
},
},
actions: {
nuxtServerInit({ commit }, context){
axios.get(api-link-to-fetch-data-from-backend))
.then(res => {
commit("setOrganisation", res.data[0])
})
.catch(e => context.error(e));
}
},
getters: {
getOrganisation(state){
return state.organisation;
}
}
})
}
This data needs to be used in the navigation bar. So it is being fetched from the store in layout/default.vue in the created method like this,
created() {
this.organisation = this.$store.getters.getOrganisation;
}
As nuxtServerInit is the first function that is called in the nuxt lifecycle why is the data not being stored when any link from the website is accessed directly?
Example here:
Post
This information about the recent videos gets set after refreshing the page.

Related

Redux toolkit createSlice not working as expected (state is not modified thought action seem to be fired)

I am new to Redux toolkit. I have a working app in which would like to implement it in place of existing "regular" reducer.
import { createSlice, PayloadAction } from "#reduxjs/toolkit";
import { SelectedMinifig } from "types";
const initialState = {} as SelectedMinifig;
const selectedMinifigSlice = createSlice({
name: "selectedMinifigX",
initialState,
reducers: {
setSelectedMinifigX(state, action: PayloadAction<SelectedMinifig>) {
state = action.payload;
console.log("state and action payload from slice", state, action.payload);
},
},
});
export default selectedMinifigSlice.reducer;
export const { setSelectedMinifigX } = selectedMinifigSlice.actions;
Please note that in the code I use postfix "X" to differentiate new names from existing ones.
From the above slice, exports are consumed like this:
import selectedMinifigReducer from "reduxware/reducers/selectedMinifigSlice";
import { partsApi } from "../api/partsApi";
const rootReducer = combineReducers({
fetch: fetchReducer,
selected: selectedReducer,
teasers: teasersReducer,
selectedMinifigX: selectedMinifigReducer,
[partsApi.reducerPath]: partsApi.reducer,
});
Above I consume reducer, and with two files below I consume action (the latest file is my usual workaround not to useDispatch in components directly):
index.ts:
export { setSelectedMinifigX } from "reduxware/reducers/selectedMinifigSlice";
useDispatchAction.ts
import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
import { actionCreators } from "reduxware";
const useDispatchAction = () => {
const dispatch = useDispatch();
return bindActionCreators(actionCreators, dispatch);
};
export default useDispatchAction;
The actions are fired like this (the new action is setSelectedMinifigX(selected), the old is setSelectedMinifig(selected), both with the same argument) :
onClick={e => {
e.stopPropagation();
setSelectedMinifig(selected);
setSelectedMinifigX(selected);
history(Paths.order);
}}
And in the moment of firing action, I really receive in console comment "state and action payload from slice " with expected content. That is why I claim action is actually fired.
The problem is that when I reach for state it is still empty object like initial state.
I have a component that is linked with state like below:
const mapStateToProps = (state: RootStateType) => ({
selectedMinifig: state.selected.selectedMinifig,
selectedMinifigX: state.selectedMinifigX,
});
and within this component, selectedMinifigX is an empty object. What is wrong here?
Hmm some of this looks a little foreign / extra to me.
Your reducer looks correct. Inside of your React component try to do something like this:
const SomeComponent = () => {
const dispatch = useDispatch()
const someHandlerFn = (e) => {
e.stopPropagation();
dispatch(setSelectedMinifigX(selected))
history(Paths.order);
}
return <button onClick={someHandlerFn}>Test Me</button>
}
I'm not sure if bindActionCreators is still valid redux. Was something I use to do before redux toolkit when using class based components. You should see your reducer fire inside of your reducer file.
The useDispatchAction.ts seems like extra stuff you don't need.

Update global state after RTK Query loads data

I've noticed a problem with splitting responsibilities in React components based on the fetched data using RTK Query.
Basically, I have two components like HomePage and NavigationComponent.
On HomePage I'd like to fetch the information about the user so that I can modify NavigationComponent accordingly.
What I do inside HomePage:
import { setNavigationMode } from "features/nav/navSlice";
export default function HomePage() {
const {data: user} = useGetUserDataQuery();
const dispatch = useAppDispatch();
const navMode = user ? "all-options" : "none";
dispatch(setNavigationMode(navMode)); // here I change the default Navigation mode
return <MainLayout>
<Navigation/>
<Content/>
<Footer/>
</MainLayout>;
}
The HomePage is a special Page when the NavigationComponent shouldn't display any options for the not logged in user.
Other pages presents additional Logo and Title on Nav.
React communicates:
Warning: Cannot update a component (NavComponent) while rendering a different component (HomePage). To locate the bad setState() call inside HomePage, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
Not sure what is the right way to follow.
Whether the state should be changed in GetUser query after it is loaded - that doesn't seem to be legit.
problem is dispatch calls every render. Instead you can create a navigationSlice (if you don't have already) and use extraReducers for matching your authorization action like:
extraReducers: (builder) => {
builder.addMatcher(
usersApi.endpoints.login.matchFulfilled,
(state, { payload }) => {
if (payload.user) {
state.navigationMode = "all-options"
}
}
);
}
This way, state.navigationMode will only change when authorization changes
The solution was too obvious. The dispatch should be run in useEffect.
import { setNavigationMode } from "features/nav/navSlice";
export default function HomePage() {
const {data: user} = useGetUserDataQuery();
const dispatch = useAppDispatch();
const navMode = user ? "all-options" : "none";
// changed lines
useEffect( () => {
dispatch(setNavMode(navMode));
}, [navMode, dispatch]);
// /changed lines
return <MainLayout>
<Navigation/>
<Content/>
<Footer/>
</MainLayout>;
}
Thank you #papa-xvii for the hint with changing the navMode after user login. That solves the second problem I had.
However I cannot accept the answer as it does not solve the problem I described above.

PWA - Cache won't update for offline use

I have a PWA which works fine both online and offline (but only with the initial files). However, the offline cache (let’s say a javascript file) is not being refreshed so whenever I am offline the old javascript file is used, but when online the new version is used.
On an iPad I can use Safari to go to the website and add the PWA to the home page.
If I then go offline, it works fine – all pages work etc.
But if I make a change to say a javascript file (something like adding an alert) and also change the version in my service worker, when I am online the change is reflected but when offline it remains at the older version
To clarify let’s say from the start, on going into a page it alerts “A1”
I then change the javascript to alert “A2” and change the version in the service worker.
If I run the app when online, sure enough the app says New Update Available and All Good (some alerts from the main.js file)
Then when I go into the actual page o the alert says “A2” – so all good.
Then go offline.
The alert still says “A1”
It seems that when online it uses the server latest files but when it tries to use cache the files are old and at the moment seem to be the original files.
I have read many sites on this with no success – some suggest it will sort itself in 24 hours. Some suggest setting the maxage of the service worker to 0 (but how do you do this?). Some say the files need renaming each time they change which seems very clunky.
The service worker is definitely working
main.js
$(document).ready(function () {
'use strict';
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register("/sw.js")
.then(res => {
console.log("service worker registered");
res.onupdatefound = () => {
const installingWorker = res.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller){
alert("new update available");
forceReload();
}
else {
alert("all good");
}
break;
}
}
}
})
.catch(err => console.log("service worker not registered", err))
}
});
const forceReload = () =>{
console.log("ForceReload");
navigator.serviceWorker
.getRegistrations()
.then((registrations) =>{
console.log(registrations);
//alert("reg");
Promise.all(registrations.map((r) => r.unregister()))
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
});
},
)
.then(() => {setTimeout(() => {
location.reload();
}, 500);
})
}
sw.js
let version =5; // update this to send update.
var cacheName = 'cacheV5'
var filesToCache = [
'/',
'/manifest.json',
'/index.html',
'/sales10.html',
'/getdata.html',
....
....
'/js/siteJS/sales10.js',
'/js/siteJS/getdata.js',
'/js/jquery/3.4.1/jquery.min.js',
'/js/bootstrap/bootstrap.min.js',
'/js/bootstrap/popper.min.js'
];
/* Start the service worker and cache all of the app's content */
self.addEventListener('install', function(e) {
self.skipWaiting();
e.waitUntil(
caches.open(cacheName).then(function(cache) {
return cache.addAll(filesToCache);
})
);
});
/* Serve cached content when offline */
self.addEventListener('fetch', function(e) {
e.respondWith(
caches.match(stripQueryStringAndHashFromPath(e.request.url.replace(/^.*\/\/[^\/]+/, ''))).then(function(response) {
return response || fetch(e.request);
})
);
});
function stripQueryStringAndHashFromPath(url) { //added this so when url paramerters passed grabbing the cashed js works
return url.split("?")[0].split("#")[0];
}
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
return true;
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});

ionic reload a page from another page/modal

EDIT
I noticed that the subscribe event must come first before and publish get called. But it will be silly to ask user to open TabOut page every time when app start.
I do not need to always reloading the TabOut page, so I need this event sort of method to do the job. Or else could've just call the reload on ionViewDidEnter().
I have 2 Tabs and 1 modal. /TabIn, /TabOut, and /ModalIn.
The Tabs page serve as data listing which display the data from database on ionViewDidLoad().
The ModalIn page serve as data entry for the user to key in and submit data. This page resides in the TabIn page and will get called when user clicked on each of the list of data.
After successfully submit the form in the ModalIn page I want to call refresh again on the TabOut page (no matter it has been loaded before or not). I tried using events publish it is not working. Below are my code.
ModalIn .ts
let headers: any = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }),
options: any = { "username": val, formValue },
url: any = "some_url_here";
this.http.post(url, options, headers)
.subscribe((data: any) => {
if (data.status == 'success') {
this.events.publish('shouldReloadData');
} else {
}
},
(error: any) => {
console.log(error);
});
TabOut .ts
constructor(public events: Events) {
events.subscribe('shouldReloadData', () => {
// Reload the page here
console.log("should reloadddd"); // <- This is not working
});
}
Call subscribe with this keyword inside TabOut.ts.
constructor(public events: Events) {
this.events.subscribe('shouldReloadData', () => {
});
}

VueJS: Redirecting between two pages

I use Firebase with VueJS (and VueRouter).
I have a problem with redirecting. I want to redirect between two pages. The first page is used for authentication and the second one for content that should only be visible to logged-in users.
My state holds the firebase user key (which will be populated through a mutation, that calls firebase):
state: {
user: { key: null }
}
The authentication page these lines:
beforeCreate() {
if (this.$store.state.user.key !== null) {
this.$router.replace('/')
}
}
And the secret page these:
beforeCreate() {
if (this.$store.state.user.key === null) {
this.$router.replace('/new')
}
}
But: the redirect from the authentication page to the secret page doesn't take place.
My Vue-dev-tools show that the user-key is set.
What could be the solution to this problem?
EDIT:
This is the mutation that calls Firebase and sets the user-key:
updateSession(state) {
auth.onAuthStateChanged((user) => {
if (user) {
state.user.key = user.uid
}
})
}
Here is the action:
UPDATE_SESSION({ commit }) {
commit('updateSession')
}
I call the action in my root component (App.vue):
beforeCreate() {
this.$store.dispatch('UPDATE_SESSION')
}
EDIT 2:
Now my routes array:
routes: [
{ path: '/', component: Secret },
{ path: '/new', component: Authentication }
]
Take a look at the Per-Route Guards section of the docs: https://router.vuejs.org/en/advanced/navigation-guards.html
You might want to try something like the below. By putting the beforeEnter guard on the route, you are telling Vue to do that first. The next argument tells VueRouter what to do next, and can redirect if needed or continue on to the original route.
beforeEnter(to, from, next) {
if (this.$store.state.user.key === null) {
next('/new')
}
}
EDIT
You may also want to try using push instead of replace
As per the conversation we had in the comments looks like you require this:
store.dispatch can handle Promise returned by the triggered action handler and it also returns Promise. See docs.
So you can setup the login action to retirn a promise like this:
a_logInUser: ({state, commit}, userInput) => {
return new Promise((resolve, reject) => {
firebase.auth().signInWithEmailAndPassword(userInput.email, userInput.paswword);
resolve();
});
}
Then in your authentication page where you tale the login input details and click the login button , set this up as the click handler of your login button
loginUser(){
this.$store.dispatch('a_logInUser', {email: this.email, password: this.password})
.then((result) => {
this.$router.replace('/');
}, (err) => {
// stay on this pageS
//handle login error
});
}
}