ReactJS error - “Cannot read property 'data' of undefined” - reactjs

I’m building a React form that will have several drop-down boxes populated with data from MongoDB. I’m relatively new to React and a beginner with MongoDB.
I’m starting with just trying to create a basic page with one drop-down on it. Once I get that right, I can move on to adding the other drop-downs.
I’ve been able to successfully get the data into the drop-down by just cramming all of the code into one file. Now, I’m trying to start refactoring the code by properly splitting pieces into separate files. And that’s where I’m running into problems.
I’ve split out my MongoDB data pull (using Mongo Stitch) into a separate “service” file. And I’m still getting data through that new service file. But when I then try to pull that service-file data into my main (App.js) page, I’m getting a “Cannot read property 'data' of undefined” error. So, clearly, the way I’m trying to pull the data into my App.js file is wrong. I’d appreciate any expert insights anyone could offer!
Here’s my App.js file –
import React, { Component } from "react";
import "./App.css";
import { getProjects } from "./services/svcProjects";
class App extends Component {
state = {
data: {
selProject: ""
},
Projects: []
};
async componentDidMount() {
await this.fetchProjects();
}
async fetchProjects() {
const { data: projects } = await getProjects();
console.log(projects);
this.setState({ projects });
}
render() {
return (
<div className="App">
<h1>Project Log</h1>
<label htmlFor={"selProject"}>{"Project"}</label>
<select name={"selProject"} id={"selProject"} className="form-control">
<option value="" />
{this.state.projects.map(a => (
<option key={a._id} value={a.project}>
{a.project}
</option>
))}
</select>
</div>
);
}
}
export default App;
And here’s the “projects service” file. Again, please note that the console.log statements here show that I’m still getting data back from MongoDB. I’m just not pulling the data into my App.js file the right way.
Also, by the way, I realize that having my Mongo connection info in this file is a huge security hole. I’ll be fixing that later.
import {
Stitch,
RemoteMongoClient,
AnonymousCredential
} from "mongodb-stitch-browser-sdk";
export function getProjects() {
const client = Stitch.initializeDefaultAppClient("------");
const db = client
.getServiceClient(RemoteMongoClient.factory, "-----")
.db("----------");
client.auth
.loginWithCredential(new AnonymousCredential())
.then(() =>
db
.collection("--------")
.find({}, { sort: { Project: 1 } })
.asArray()
)
.then(res => {
console.log("Found docs", res);
console.log("[MongoDB Stitch] Connected to Stitch");
return res;
})
.catch(err => {
console.error(err);
});
}

It looks like you're using destructuring to get the data member from an object returned by getProjects(), but getProjects() doesn't return an object with such a member.
Perhaps you'd like to change it to something like the following -
async fetchProjects() {
const projects = await getProjects();
console.log(projects);
this.setState({ projects });
}
Also, like #Win mentioned, the P in projects is capitalized in your state initialization but not afterwards. You might wanna fix that.

I've discovered that my problem boils down to the fact that my data isn't arriving in the componentDidMount lifecycle hook soon enough. So, by the time the process moves on, the "projects" data is still undefined. In other words, my "async / await" isn't . . . awaiting!
I'll be posting a separate question about how I can make that async wait until I actually get the data back. But again, just wanted to mark this as "solved" because the problem isn't happening downstream from the data fetch. The data simply isn't arriving soon enough.

Related

Next.js production: CANNOT READ DATA FROM FIRESTORE

I have a next.js application in which I have a real-time chat feature.
A get() request is supposed to be sent to firestore to check if chat between user A and user B already exists. If it does not already exist, a new chat between A and B is created
Problem:
The get() request to firestore always returns an empty value. So a new chat room between user A and user B is always created.
Since a new chat room is created successfully, I can write to firestore but cannot read data.
the code:
import { useEffect, useState } from "react";
import { dbs } from "../utils/firebase";
function Test() {
const [chats, setChats] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await dbs
.collection("chats")
.where("users", "array-contains", "some#email")
.get();
setChats(response);
} catch (err) {
console.log(err);
}
};
fetchData();
}, []);
console.log("chat is", chats);
return <div></div>;
}
export default Test;
What I have attempted and discovered:
I deployed an entirely new next.js app (let's call it app B) and tried to get data from the same firestore project with the same code above and it works. So my firestore configuration is fine, the code is fine, and there's nothing wrong with the deployment server.
Since there's nothing wrong with firestore or the deployment server or the code, I think something is wrong with the next.js app itself and the dependencies surrounding it. I don't know why it works in app B and not in app A.
Note, in App A:
Everything works fine in development mode, I can get the data.
I'm also using mongoDB and other third-party APIs in this app and I can successfully read and write data to these APIs. Only the get() request to firestore is not working. which made me think the problem must be with my firestore config but app B proves that's not the case.
I'm using google OAuth with mongoDB and next-auth to authenticate my users in app A. In app B I'm only using google OAuth without the DB.
I could show the code for both app A and app B but they are exactly the same. The code under the _app.js file is the same, the way I structure it is also the same. In both app A and app B, I called the get() request to firestore from a Test.js component in a test.js page but the get() request only returns value in app B and always returns null in app A.
So basically the only difference is app A is an actual app with plenty of dependencies, libraries, and files. app B is just a test app.
My question now is:
Could the read operation to firestore be affected by other dependencies or npm libraries in the project? or could it be affected by using another DB to get the auth session context?
Why is the write operation successful but not the read?
Why does it work in dev mode but not in prod mode? is there any difference between retrieving data from firestore in dev mode vs prod mode?
As explained yesterday, you aren't paying close enough attention to the intermediate states properly and it is likely that NextJS is tripping up because of it.
On the first render of your current code, chats is an empty array. Even so, once the promise fulfills, you update chats to a QuerySnapshot object, not an array.
import { useEffect, useState } from "react";
import { dbs } from "../utils/firebase";
function Test() {
const currentUserEmail = "some#email"; // get from wherever
const [chatsInfo, setChatsInfo] = useState({ status: "loading", data: null, error: null });
useEffect(() => {
let unsubscribed = false;
const fetchData = async () => {
return dbs
.collection("chats")
.where("users", "array-contains", currentUserEmail)
.get();
};
fetchData()
.then(() => {
if (unsubscribed) return; // do nothing
setChatsInfo({
status: "loaded",
data: response.docs, // QueryDocumentSnapshot[]
error: null
});
})
.catch((err) => {
if (unsubscribed) return; // do nothing
setChatsInfo({
status: "error",
data: null,
error: err
});
});
};
// return a unsubcribe callback that makes sure setChatsInfo
// isn't called when the component is unmounted or is out of date ​
​return () => unsubscribed = true;
​ }, [currentUserEmail]); // rerun if user email changes
​
​ const { status, data: chats, error } = chatsInfo;
console.log(JSON.parse(JSON.stringify({ status, chats, error }))); // crude object copy
switch (status) {
case "loading":
return (<div>Loading...</div>);
case "error":
return (
<div class="error">
Failed to load data: {error.code || error.message}
</div>
);
}
// if here, status is "loaded"
if (chats.length === 0) {
// You could also show a "Message Someone" call-to-action button here
return (
<div class="error">
No chat groups found.
</div>
);
}
// stamp out list of chats
return (<>
{chats.map((chatDoc) => {
return (
<div key={chatDoc.id}>
{JSON.stringify(chatDoc.data())}
</div>
);
})}
</>);
}
export default Test;
Notes:
A decent chunk of the above code can be eliminated by using an implementation of useAsyncEffect like #react-hook/async and use-async-effect. These will handle the intermediate states for you like loading, improper authentication, unmounting before finishing, and other errors (which are all not covered in the above snippet). This thread contains more details on this topic.
I highly recommend not using email addresses in their raw form for user IDs. With the current structure of your database, anyone can come along and rip all the emails out and start spamming them.
Each user should have some private identifier that doesn't reveal sensitive information about that user. This could be a Firebase Authentication User ID, the user's email address hashed using md5 (which also allows you to use Gravatar for user profile pictures) or some other ID like a username. Once you have such a user ID, you can use the approach outlined in this thread for handling messages between users.

Why is my state not properly rendered in reactjs?

In my project I use ReactJS in combination with redux and firebase.
Creating a thunk to make async calls to firebase and store the data in redux.
When I'm fetching my files from firebase storage.
Using this method:
try {
let list = [];
await storage
.ref()
.child(path)
.listAll()
.then((res) => {
res.items.forEach((item) => {
storage
.ref()
.child(item.fullPath)
.getDownloadURL()
.then((urlRes) => {
list.push({
name: item.name,
url: urlRes,
});
});
});
});
dispatch(getFileActionSuccess(list));
This method works as intended.
It returns an array of files with their url to view/download them.
The problem is when I try to access this object in my state, it returns an empty array.
Even though when checking using Redux Devtools, I can clearly see that after the list was dispatched. And I could see the correct data.
Devtools image
Note: this is not the real code but a representation
function page() {
getFiles();
<filesList/>
}
function filesList() {
const files = useSelector((state) => state.files, _.isEqual);
console.log(files);
return (..insert render..);
}
But when logging the files. It shows an empty array at first. But when expanding it, it shows the correct data. But it doesn't render it. As I don't understand why it isn't showing like it is supposed to I no longer know what to do and how to fix this.
Simply fetch the data on component mount and component update, and update your state accordingly.
If you’re using React Hooks, you can use React.useState() and give it a dependency. In this case the dependency would be the part of your state which will update upon completion of your HTTP request.

ReactJS: Pass object into fetch promise

I'm kind of new react, and this quill plugin is really confusing me. I'm using react-quilljs to display an editable rich text field, which is supposed to be pre-populated with a value retrieved using fetch from my API. Seems pretty simple, right? But I'm getting the error 'quill is undefined' in the fetch callback.
import React, { useState, useEffect } from "react";
import { useQuill } from "react-quilljs";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
// see https://www.npmjs.com/package/react-quilljs
export default function View(props) {
const [group, setGroup] = useState([]);
const { quill, quillRef } = useQuill({});
useEffect(() => {
fetch('/api/groups/' + props.id , {
method: 'GET'
})
.then(res => res.json())
.then((data) => {
setGroup(data);
quill.setContents(JSON.parse(data));
})
.catch(console.log);
}, [quill]);
return(
<div >
<div id="descriptionInput" ref={quillRef} />
</div>
);
}
Of course I've omitted a lot of the code, but I think it should be enough to illustrate the problem. I think, basically the question is, how do I pass the quill object into the fetch promise?
I have searched for the answer but for some reason can't find anything on this.
I looked through the documents and found this:
quill.clipboard.dangerouslyPasteHTML();
I have made a working sample for you:
https://codesandbox.io/s/epic-stonebraker-itt06?file=/src/App.js:401-469
After some more inspection, it turns out useEffect is being called multiple times, and quill is not available right away (as Asher Lim notes). So adding a check if (quill) inside the fetch promise solves the problem.
Of course this means that the fetch is being done more times than necessary, which can be solved with some more refactoring.

React – Async / Await doesn’t seem to be waiting

I had previously posted this problem under the error message of “Cannot read property 'data' of undefined”. But in the process of digging into it for several hours, I’ve discovered that my problem really boils down to the fact that my “async / await” doesn’t seem to be . . . . awaiting! And yes, I did check the several other versions of this question that have already been asked here. :)
I’m building a React form that will have several drop-down boxes populated with data from MongoDB. I’m relatively new to React and a beginner with MongoDB.
I’ve been able to successfully get the data into the drop-down by just cramming all of the code into one file. Now, I’m trying to start refactoring the code by properly splitting pieces into separate files. And that’s where I’ve run into this “data delay” issue.
When “componentDidMount” calls the “fetchProjects” function, that function goes and grabs a list of projects from MongoDB using the “getProjects” function in a “projects service” file. When the console.log within “fetchProjects” runs, it’s coming back as undefined. But then after the data set comes back as undefined (and errors out the process), I do get a console log of the projects object array from the “getProjects” function.
I’ve been able to make this process work with hard-coded object array data in the “getProjects” function, so that tells me that the problem lies in the amount of time required to actually get the data back from MongoDB.
Please tell me there’s a way to solve this without using Redux! :D
Here’s my App.js file –
import React, { Component } from "react";
import "./App.css";
import { getProjects } from "./services/svcProjects";
class App extends Component {
state = {
data: {
selProject: ""
},
projects: []
};
async componentDidMount() {
await this.fetchProjects();
}
async fetchProjects() {
const { data: projects } = await getProjects();
console.log(projects);
this.setState({ projects });
}
render() {
return (
<div className="App">
<h1>Project Log</h1>
<label htmlFor={"selProject"}>{"Project"}</label>
<select name={"selProject"} id={"selProject"} className="form-control">
<option value="" />
{this.state.projects.map(a => (
<option key={a._id} value={a.project}>
{a.project}
</option>
))}
</select>
</div>
);
}
}
export default App;
And here’s the “projects service” file. Again, please note that the console.log statements here show that I’m still getting data back from MongoDB. That data is just taking too long to arrive back in the App.js file.
Also, by the way, I realize that having my Mongo connection info in this file is a huge security hole. I’ll be fixing that later.
import {
Stitch,
RemoteMongoClient,
AnonymousCredential
} from "mongodb-stitch-browser-sdk";
export function getProjects() {
const client = Stitch.initializeDefaultAppClient("------");
const db = client
.getServiceClient(RemoteMongoClient.factory, "-----")
.db("----------");
client.auth
.loginWithCredential(new AnonymousCredential())
.then(() =>
db
.collection("--------")
.find({}, { sort: { Project: 1 } })
.asArray()
)
.then(res => {
console.log("Found docs", res);
console.log("[MongoDB Stitch] Connected to Stitch");
return res;
})
.catch(err => {
console.error(err);
});
}
I think adding a return into your getProjects() service will solve your issue.
import {
Stitch,
RemoteMongoClient,
AnonymousCredential
} from "mongodb-stitch-browser-sdk";
export function getProjects() { //add async if you need to await in func body
const client = Stitch.initializeDefaultAppClient("------");
const db = client
.getServiceClient(RemoteMongoClient.factory, "-----")
.db("----------"); // if these above are async, then await them as well.
// added return keyword here
return client.auth // should return Promise to await in your component
.loginWithCredential(new AnonymousCredential())
.then(() =>
db
.collection("--------")
.find({}, { sort: { Project: 1 } })
.asArray()
)
.then(res => {
console.log("Found docs", res);
console.log("[MongoDB Stitch] Connected to Stitch");
return res;
})
.catch(err => {
console.error(err);
});
}
Edit 1:
For refactoring, I think pairing redux and redux-saga will give you very good separation of concern and a way to easily write test if you plan to do so.
But overall, I think this tweak above can at least solve your issue.

How to use helper functions with Apollo GraphQL

I find myself in situations with React/Redux apps where I require a lot of functions to transform data when it comes back from the server by using libaries like moment or jwtDecode. For example:
function mapDispatchToProps(dispatch) {
return {
getAllTokens: () => {
dispatch(getAllTokens());
}
};
}
On page load, I run this.props.getAllTokens() to bring back all tokens from the server stored in the database which I'd like to show on the page (for example).
The dispatch uses Redux Thunk to perform some operations on the data and send to the store:
export const getAllTokens = () => dispatch => {
apiGetAllUsers()
.then(tokens => {
let newFormat = tokens.message.map(token => {
if (token.refreshToken) {
let decoded = jwtDecode(token.refreshToken);
let expiresIn = moment.unix(decoded.exp).fromNow();
return {
refreshToken: token.refreshToken,
email: token.email,
expiresIn
};
}
});
dispatch(adminTokensReceived(newFormat));
})
};
This function getAllTokens is in another file to help the component keep lean. This approach works fine for Redux, but when it comes to using Apollo instead, how would I update the data before adding it back to the component props?
I have came across this link which shows how to update data from a query, but I don't see many examples using it, so I wondered if I'm missing something fundamental?
So my questions is, what is the best way to use helper functions as I've described above?
Thanks

Resources