When a user logs out, we get a number of errors in our components due to the redux store being cleared out before you are taken to the login page.
We have some components that use the useQueryWithStore. Here is an example:
const { data, total, error, loaded } = useQueryWithStore({
type: "getList",
resource: "tasks",
payload: {
pagination: { page: 1, perPage: 5 },
sort: { field: "date_end", order: "ASC" },
},
});
Then we are checking whether the data is loaded and there are no errors before rendering the component.
return (
{!loaded && !error ? (
<Loading />
) : (
...our component rendering data...
)
)
The problem is that when a user logs out, it first clears the redux store before navigating to the login page. That means that both !loaded and !error are true and it attempts to render the component. But because the redux store has been cleared, data is undefined.
How would you handle this best?
Add default value to data? - const {data = [], total, error, loaded}
Check if data is undefined? - {!loaded && !error && data}
Use optional chaining? - Any references to data could use optional chaining - data?.map(...map function...)
Make pull request to change order of logout? - Instead of clearing redux first we could navigate to login page or logged out page and then clear redux store.
A better way I haven't thought of?
Thoughts?
Related
I'm building an app with NextAuth's email provider as the sole means of user registration and login.
In local development everything worked fine, but when deploying the app to Vercel I keep getting Unhandled Promise Rejection errors from Mongo, so I decided to switch to NextAuth's unstable_getServerSession and everything is running very smoothly now.
Before making the switch I used an extra parameter to communicate whether the user was just logging in or newly signing up. Based on this, I sent different emails (a welcome email with login link vs. a shorter message with just the login link).
Here is how the [...nextauth].js file looked before:
export default async function auth(req, res) {
const signup = req.body.signup || false
return await NextAuth(req, res, {
adapter: MongoDBAdapter(clientPromise),
...
providers: [
EmailProvider({
...
sendVerificationRequest(server) {
customLogin(
server.identifier,
server.url,
server.provider, // passing the above data for email content
signup // custom parameter to decide which email to send
)
}
})
],
...
})
}
I adjusted the code sample from the documentation, but wasn't able to pass a custom parameter to it.
Here is my new code:
export const authOptions = {
adapter: MongoDBAdapter(promise),
...
providers: [
EmailProvider({
...
sendVerificationRequest(server) {
customLogin(
server.identifier,
server.url,
server.provider,
)
}
})
],
...
}
export default NextAuth(authOptions)
The customLogin function doesn't do anything except construction the different email options.
Among the things I have tried are wrapping authOptions in a handler function with req and res, setting up a separate API route, and passing parameters to the NextAuth function, but none of those worked.
Any advise on how to implement a custom parameter here would be highly appreciated.
I have a nuxt application. One of the components in it's mounted lifecycle hook is requesting a value from the state store, this value is retrieved from local storage. The values exist in local storage however the store returns it as undefined. If I render the values in the ui with {{value}}
they show. So it appears that in the moment that the code runs, the value is undefined.
index.js (store):
export const state = () => ({
token: process.browser ? localStorage.getItem("token") : undefined,
user_id: process.browser ? localStorage.getItem("user_id") : undefined,
...
Component.vue
mounted hook:
I'm using UserSerivce.getFromStorage to get the value directly from localStorage as otherwise this code block won't run. It's a temporary thing to illustrate the problem.
async mounted() {
// check again with new code.
if (UserService.getFromStorage("token")) {
console.log("user service found a token but what about store?")
console.log(this.$store.state.token, this.$store.state.user_id);
const values = await ["token", "user_id"].map(key => {return UserService.getFromStorage(key)});
console.log({values});
SocketService.trackSession(this, socket, "connect")
}
}
BeforeMount hook:
isLoggedIn just checks that the "token" property is set in the store state.
return !!this.$store.state.token
beforeMount () {
if (this.isLoggedIn) {
// This runs sometimes??? 80% of the time.
console.log("IS THIS CLAUSE RUNNING?");
}
}
video explanation: https://www.loom.com/share/541ed2f77d3f46eeb5c2436f761442f4
OP's app is quite big from what it looks, so finding the exact reason is kinda difficult.
Meanwhile, setting ssr: false fixed the errors.
It raised more, but they should probably be asked into another question nonetheless.
I have a website which contains kind of blog pages where people can post issues and other people can comment on them.
But when i go to an issue route it gets the data from firebase when the route is the same as the saved slug in the database (not a really good way to do it like this). The data is displayed to the browser but there is one major issue if you reload the page netlify says the page could not be found.
I am loading the data from mounted() lifecycle it might be better to do this on asyncdata()
Here is my code:
mounted() {
self = this;
const issues = firebase.database().ref('Issues/')
issues.once('value', function(snapshot){
snapshot.forEach(function(childSnapshot) {
const data = childSnapshot.exportVal()
if(self.$nuxt.$route.path == "/Issues/"+data.Slug || self.$nuxt.$route.path == "/issues/"+data.Slug ) {
self.allIssuetitels.push(data.Titel)
self.allIssueonderwerpen.push(data.Onderwerp)
self.allteksten.push(data.Tekst)
self.user = data.User
self.profielfoto = data.Profielfoto
self.Slug = data.Slug
}
})
})
const reacties = firebase.database().ref('/Reacties')
reacties.once('value', function(snapshot) {
snapshot.forEach(function(childSnapshot) {
const data = childSnapshot.exportVal()
if(self.$nuxt.$route.path == '/Issues/'+data.Slug || self.$nuxt.$route.path == '/issues/'+data.Slug) {
self.reaprofielfoto.push(data.Profielfoto)
self.reauser.push(data.User)
self.allreacties.push(data.Reactie)
}
})
})
},
When i test it local it doesn't give me the page not found error. Is there any way of fixing this issue?
Here is where it does not work:
https://angry-lalande-a5e5eb.netlify.app/issues/3
For something like this where you are enabling users to create new pages and to have lots of user-generated content that is frequently update, you'd be well-served to look into server-side rendering.
https://v2.vuejs.org/v2/guide/ssr.html
I'd suggest, if you want to do that, you should migrate over to Nuxt as it makes things like this much easier.
However, there are quite a few caveats with going SSR - notably you'll need to run and manage a server.
If you don't want to look into SSR, you could use dynamic routes as shown here
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ path: '/issue/:id', component: Issue }
]
})
You could then have the Issue component display a skeleton loader or another loading indicator while the content is fetching.
https://vuetifyjs.com/en/components/skeleton-loaders/
Inside my Redux store I recently started getting PERFORM_ACTION actions, which wrap my actual actions as follows:
{
type: PERFORM_ACTION,
action: {
type: REAL_ACTION,
payload: 123
}
}
I was not able to find any answer to this pattern neither in the documentations, nor in Google. The only suggestions that Google included were just references to this type of action without any explanation of what is it and why does it appear in applications.
So, what is this action?
redux dev tool extension wrap your actions with it's own action, what you can do is change the order of the middlewares load,
const store = createStore(
rootReducer,
compose(
applyMiddleware(
/* ---- middlewares ---- */
),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
)
);
if you place the the redux devtools middleware before your middlewares, you will get the warped action.
Following the style of this Facebook app sample using Redux and Flow together, I made an action type in this manner:
type Action =
| { type: 'ADD_FILES', files: Array<{ id: number, file: File }> }
| { type: 'HANDLE_IMAGE_PUBLISHED', id: number, name: string }
| { type: 'SET_IMAGE_UPLOAD_PROGRESS', id: number, progress: number }
;
But I've found that when I try to process my actions with a reducer, Flow complains if I try to access the name or progress properties, saying "Property not found in object type".
That is, in my reducer, if I check that action.type === 'HANDLE_IMAGE_PUBLISHED' and then access action.name, Flow complains. And the same thing goes for for accessing action.progress property when action.type === 'SET_IMAGE_UPLOAD_PROGRESS'. Both these property accesses should be legit under their respective circumstances, as far as I can tell, but Flow complains.
Yet for some reason it's OK for me to access action.id anywhere, even though one of the types in my union doesn't specify an id property. I'm very confused.
Here is a live demo in the Flow REPL. What am I doing wrong?
This is simply a case of a type refinement invalidation:
https://flow.org/en/docs/lang/refinements/#toc-refinement-invalidations
Because you are using the value in a callback, Flow pessimistically assumes that you could have re-assigned action before the callback runs (it does not know that the map callback is called immediately). It also does not do the analysis to see that there is no place, in fact, that you re-assign it.
All that's needed is to pull the action out as a const:
export default (state: Array<ImageRecordModel> = [], action_: Action): Array<ImageRecordModel> => {
const action = action_;
(tryflow link)
You may also want to consider enabling const params in your .flowconfig. This does basically what you expect: treats all params as const:
[options]
experimental.const_params=true