findOne throwing undefined even though the data is there - javascript

I'm new to Meteor so I've been playing around and now I'm stuck with this problem.
I'm using React Router to try to show a theme based in the URL /(:userId). If there's no userId inserted into the URL it should show the current user's theme and If there's no current user it should show a default theme.
It's working randomly. Sometimes I get correct theme, sometime it throws undefined when reading themeColor even though the data is there. I can see with console.log that it always get the right Id, but still findOne can throws undefined. It specially happens when I change the URL (/xyz) and go back to the default one (/).
I verified with the console that userId is the actual owner of themeColor and themeTextColor.
I'm using React, React-router, autopublish. I removed insecure.
getMeteorData() {
var currentViewedPageId = this.props.params.userId? this.props.params.userId:(Meteor.userId()?Meteor.userId():false);
console.log(currentViewedPageId); //Allways right
console.log(Personalization.findOne({owner: currentViewedPageId}).themeColor); //Sometimes undefined, sometimes not
if(currentViewedPageId)
{
return {
currentUser: Meteor.user(),
themeColor: Personalization.findOne({owner: currentViewedPageId}).themeColor,
themeTextColor: Personalization.findOne({owner: currentViewedPageId}).themeTextColor
};
}
return {
currentUser: Meteor.user()
}
},

Since the code is working sometime. There can be some test data which doesn't match the schema. So test all data in the collection.

Related

Dynamic routing in Next.js gets TypeError: the ‘id’ argument must of type ‘string’. Received null

I’m running into an issue with generating dynamic pages in Next.js. I’m pulling data from Sanity, and I think I’ve got the code right, but every time I try load the page I get a type error - “the ‘id’ argument must be of type string. Received null”. The file path is ‘/post/[slug].js’, so that’s the variable name I’ve used throughout - I’m not sure where id is coming from, or if it’s something extra I need to pass, or if I’m not passing my slug right!
I’ve asked in the Sanity forums, and it doesn’t seem to be an API issue - I’m able to pull data in other parts of the app with no problem. In the console, it seems like the page compiles successfully, but this error comes up when attempting to load it. I’ve tried it with an empty div in the page component, to make sure it’s nothing in the presentation logic, with no luck.
I’ve put the full error message in a gist, and it looks like something with jest or next-server. For the life of me I can’t figure it out!
import client from '../../client’
const Post = ({ post }) => {
...
}
export default Post
export const getStaticPaths = async () => {
const posts = await client.fetch(`*[_type == "blog"]{ slug }`)
const paths = posts.map(post => ({
params: {
slug: post.slug.current
}
}))
return {
paths,
fallback: false }
}
export const getStaticProps = async ({ params }) => {
const post = await client.fetch(`*[_type == "blog" && slug.current == $slug]{ title, date, link, excerpt, body, slug }[0]`, {slug: params.slug})
return {
props: { post }
}
}
UPDATE: this seems to be an issue with either play.js or the configuration it uses - trying in a desktop environment doesn’t result in the same error.
I think the issue is your file name. the name should only be [slug].js, it should be located in pages/post. It should work perfectly when you access the page via the URL http://localhost:xxx/post/pulled-sanity-slug

The client-side rendered virtual DOM tree is not matching

i have following problem:
In the title you can read my error message.
I have in my layouts/default.vue an code that checks if i have an cookie stored in this browser.
If its the case, i get the data from the cookie and store it in my store/index.vue
if (getCookie("jwt") != null) {
this.$store.commit("storageToken", getCookie("jwt"));
}
And in my store i got this code:
storageToken: function(state, payload) {
state.token = payload;
state.loggedIn = true;
},
The problem here is now the state.loggedIn. If i set it from false to true, it shows me the error because have in layouts/default.vue an nuxt-link like this:
<nuxt-link v-if="$store.state.loggedIn" class="loginbtn" to="dashboard">Dashboard</nuxt-link>
If i remove my nuxt-link tag, the loggedIn value gets changed without problems.
I found a way to make it work, if i wrap it in client-only.
<client-only>
<nuxt-link v-if="$store.state.loggedIn" class="loginbtn" to="dashboard">Dashboard</nuxt-link>
</client-only>
It works now, but the problem i have now is a SEO problem. My intern links arent visible for the google crawler now. When i analyze my website it says "could not find any intern links"
So how do i make this work again? I found no answer not here also not on google

React Native Chat Using Mongodb

I'm pretty new to react native and I am currently developing a chat/forum application. Recently, I have been having some trouble trying to create a direct message section for my app. I don't know how to connect my database to my frontend. Here is the issue:
I use a mongodb database that consists of two collections: messages and conversations.
Each conversation has a unique Id and each message has a chatId that corresponds to the conversation that it belongs to.
In my react native app, inside a Direct Message component, I have a flatlist that displays the different chats.
When the Direct Message component willMount() I call an async function, getChats(), that fetches the chats from the database that the current user is part of. The fetched chats are then set to the state.
Then, inside getChats(), after the chats are set to the state, I have a for loop that basically loops through the entire this.state.chats array and then calls a function getMessages(this.state.chats[i].id) which fetches all the messages that share the same chatIds as the chats ids. The fetched messages are then added to this.state.messages.
Finally, a flatlist with the props,
keyExtractor={(item)=>item._id}
data ={this.state.chats}
renderItem={({item})=>this._renderChats(item)}
extraData = {this.state}
,renders the chats.
I want to be able to show the latest messages content and sender in the chat View, however, an error saying that the messages content is undefined.
I think this may be due to the fact that messages aren't set to the state before the chats are rendered, but I'm not sure.
How would you solve this? Would you change the frontend or the backend? Would you change both? Should I share my code to make it easier to understand the problem?
Thanks in advance.
Very clear explanation of the problem! In short:
How to build a chat page with conversations overview (showing last message for each conversation) - using React Native, MongoDB, NodeJS and Express
Some notes:
use consistent naming, ie. rename chatId to conversationId and chats to conversations
try to minimize the internet requests - because they're resource intensive and slow
right now, your algorithm makes conversations.count+1 requests each time the page is opened, could be just 1
for the first page, you only need the last message in each conversation
load the rest of the messages for a conversation when its page is opened
thereby, you don't need the extraData field
(though caching, see additional notes)
eg. using GraphQL query('currentUser.conversations { text, messages(limit 1), ... }')
eg. using rest + mongoose:
// controller
// see https://stackoverflow.com/questions/32207457/node-js-mongoose-populate-limit
// populate the first item in conversation.messages
const conversations = ()=> db.Conversations
.find(...)
.populate({ path: 'messages', options: { limit: 1, sort: { created: -1} }}))
.exec(...)
// router
router.get('/conversations', ({user}, res)=>
getConversations({user})
.then(data=> res.send({data}))
.catch(error=> res.send({error}))
)
(this assumes messages are a virtual property on conversations)
// schema
const messageSchema = new Schema({
text: String, ...
conversationId: {type: Schema.ObjectId, ref: 'conversation'}
})
const conversationSchema = new Schema({
participants: [{type: Schema.ObjectId, ref: 'user'}]
})
// add a "virtual" field to conversation called messages
// https://stackoverflow.com/questions/43882577/mongoosejs-virtual-populate
conversationSchema.virtual('messages', {
ref: 'message',
localField: '_id',
foreignField: 'conversationId'
})
// make sure the "virtual" fields are included in conversion
conversationSchema.set('toObject', { virtuals: true });
conversationSchema.set('toJSON', { virtuals: true });
assume that all data is broken; eg. the app should not crash if the message data is missing. Check that the property exists before accessing it, and convert it into the expected data type before proceeding.
Make sure the errors aren't "swallowed"; eg. make sure to always have a .catch and log the error if you have a .then, and paste the error messages to the question, if any.
Uncaught TypeError: Cannot read property 'c' of undefined, when doing a.b.c, where you know that a is an object, can be avoided by first checking; if (a.b) use(a.b.c), or with shortcircuit: a.b && use(a.b.c)
correct hypothesis, what happend was:
ConversationsPage initialized, state.conversations is Empty
willMount called -> fetchAllConversations started, takes time, async
willMount ends -> render called -> empty list rendered
fetchAllConversations' first request finishes -> setState{conversations} -> render called again -> full list rendered (in your case, it crashed because the last message field was missing)
fetchAllConversations invokes fetchMessagesForConversations, which makes many api requests, and possibly calls setState multiple times (ineffective), which in turn causes re-render
don't forget the loading state
state = {loading: false}
render () {
const {loading} = this.state
return <FlatList renderHeader={()=> loading? <ActivityIndicator .../>: null} .../>
}
instead, a simple fix would be to call setState after all messages have been loaded:
async fetchAllConversations () {
const conversations = await api.conversations()
await Promise.all(conversations.map(c=> c.messages = await api.messagesForConversationId(c._id)))
// similar to for (let i=0; i<conversations.length; i++) {
// const c = conversations[i]; c.messages = await api.messagesForConversationId(c._id)}
return conversations
}
async reload () {
this.setState({loading: true})
this.fetchAllConversations()
.then(conversations=> this.setState({conversations}))
.catch(showError)
// stop loading both on error and success
.then(()=> this.setState({loading: false}))
}
state = {loading: false, conversations: []}
willMount () { this.reload() }
though a better solution would be to replace fetchAllConversations from above, assuming the use of virual property and population mentioned above server-side:
async fetchAllConversationsIncludingLastMessage () {
const conversations = await api.conversationsWithLastMessage()
return conversations
}
this would reduce the flow to:
ConversationsPage initialized, state.conversations is Empty
willMount called -> reload started, takes time, async
willMount ends -> render called -> loading indicator rendered
reload's only request finishes -> setState{conversations} -> render called again -> full list rendered
Additional notes:
look into Docker for simplified server setup (ie. to get MongoDB + Node.js running together)
I presume you've got a middleware that does the authentication, + correct authorisation logic in the query (eg. only find the conversations/messages the authorised user should have access to)
ie. db.Conversation.find({participants: {$includes: req.user._id}}) // pseudocode
ie. in messages, first see if the conversation with that id has the user as participant
how do you handle pagination? (eg. to prevent slow data fetching and slow UI when there are many posts) (tips: use a "cursor" instead of "offset" - prevents duplication issue etc)
Use some library to cache the data locally to improve perceived and actual loading time.
Central state management (using eg. Mobx, Redux, Apollo...) solves some of that
if you're going to use REST anyhow, make an API wrapper helper + look into mobx
otherwise, check out GraphQL and Apollo or similar

Retrieving URL From Just Prior to Logout in Angular 2 App

I am trying to obtain the last active url prior to logging a user out of my Angular 2 app, so that when they log back in it will redirect them to that component/page. The thing is, while I'm able to obtain the active url with this.router.routerState.snapshot['url'] -- this always provides me with the result /login -- which is the url they're re-directed to right AFTER logout takes place. What I need to do is find a way to get the url JUST PRIOR to logout happening. I tried doing this in my logout function in my authenticationService:
logout()
{
let activeUrl = this.router.routerState.snapshot['url'];
console.log(activeUrl);
this.apiService.logout();
// remove user from local storage to log user out
sessionStorage.removeItem('currentUser');
console.log('User successfully logged out');
// Trigger event
this.change.emit(this);
}
But even though the activeUrl is being console.logged before this.apiService.logout() is called (see order of operations in above function), I still end up getting /login logged to the console. Is this to be expected? How can I get around this?
EDIT: After some reflection it occurs to me now that the problem may be that I'm calling the logout() function from within the ngOnInit life cycle hook of my login component - so at that point '/login' is going to be the result regardless:
ngOnInit()
{
this.authenticationService.getActiveUrl();
this.authenticationService.logout();
}
So the pertinent question becomes: how do I capture the active URL just prior to being logged out?
In the Angular documentation it states that RouterState contains RouterStateSnapshot so to me it seems that the toString() is called when you log it to console but you still keep a reference in your activeUrl which may change.
Try:
let activeUrl = this.router.routerState.snapshot['url'].toString();

Cannot initialise store from localStorage on App initialization

I'm struggling to initialize my Vuex store with the account details of the logged in user from localStorage.
I've been through as many examples as I can of Auth using Nuxt, and none of them demonstrate how on the client side to pull an authToken from localStorage to re-use with subsequent API requests when the user refreshes the page or navigates to the App
Disclaimer: I'm not using Nuxt in SSR (this might affect your answer).
What is annoying is that I can actually load from localStorage and initialize my state but then it gets overwritten. I'll show you what I mean with this small code example:
buildInitialState () {
if (!process.server) {
// Query the localStorage and return accessToken, refreshToken and account details
return {accessToken: <valid-string>, refreshToken: <valid-string>, account: <valid-json-blob>}
} else {
// Return "empty map", we use the string "INVALID" for demonstration purposes.
return {accessToken: "INVALID", refreshToken: "INVALID", account: "INVALID"}
}
}
export const state = () => buildInitialState()
If I put a breakpoint on buildInitialState I can see that I correctly initialize the state with the value of localStorage, i.e. I get the accessToken and refreshToken, etc.. back.
All seems well.
But then .....in another part of the program I'm using Axois to send requests, and I use an axios interceptor to decorate the request with the accessToken. To do this I have to stick it into a plugin to get access to the store.
Something like so:
export default ({ app, store }) => {
axios.interceptors.request.use((config) => {
const accessToken = _.get(store.state, ['account', 'accessToken'])
if (accessToken) {
config.headers.common['x-auth-token'] = accessToken
}
return config
}, (error) => Promise.reject(error))
}
Here the store is closed over in the arrow function supplied to axios so when I go to send the request it sees if there is a valid accessToken, and if so then use it.
BUT, and here's the kicker, when a request is made, I look at the store.state.account.accessToken and low and behold its been reinitialized back to the value of "INVALID".
What? Que?
It's almost like the store was reinitialized behind the scenes? Or somehow the state in the plugin is "server side state"?? because if I try and log buildInitialState I don't get any messages indicating that the path that produced a map with INVALID is being run.
Basically, I don't thoroughly understand the initialization pathway Nuxt is taking here at all.
If any Nuxt masters could help me out understand this a bit more that would be great, it's turning into a bit of a show stopper for me.
Essentially! All I want to be able to do is save the user so that when they refresh their page we can keep on running without forcing them to re-login again....I thought that would be simple.
Thanks and regards, Jason.
I've solved this with a bit of experimentation and comments from other posters around what is called SSR and SPA.
Firstly, this https://github.com/nuxt/nuxt.js/issues/1500 thread really helped me and the final comment from #jsonberry steered my mind in the right direction, away from fetch and asyncData.
I finally had a bit more of an understanding of how NUXT.js was separating SSR and SPA calls.
I then tried #robyedlin suggestion of putting localStorage initialization in the created() method for my main layout/default.vue page.
While I made progress with that suggestion it turns out created() is also called SSR and I was still trying to initialize my store from credentials that weren't accessible.
Finally, moving the initialization to mounted() did the trick!
So in summary:
My account store is left alone, I don't try and initialize it when it is created (it's just overwritten at some point when the SSR stuff runs)
On mounted() in layout/defualt.vue I read from localStorage and initialize the account store so I can start making API requests with the appropriate accessToken.
That seems to have done the trick.

Categories

Resources