I work by a tutorial to create a fullstack react web app with backend connected to mongo , now while trying to merge it with my previous code I getting a syntax error..
i try to search in google but none of it help
this is my console error
module build failed(from ./node_modules/babel-loader/lib/index.js);
syntaxError: c:/users/aviram/zofim/client/src/app.js: unexpected token (49:16)
49| getDataFromDb = () => {
this is my code
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
// initialize our state
constructor(props){
super(props);
this.state = {
data: [],
id: 0,
message: null,
intervalIsSet: false,
idToDelete: null,
idToUpdate: null,
objectToUpdate: null
};
this.getDataFromDb = this.getDataFromDb.bind(this);
}
// when component mounts, first thing it does is fetch all existing data in our db
// then we incorporate a polling logic so that we can easily see if our db has
// changed and implement those changes into our UI
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 1000);
this.setState({ intervalIsSet: interval });
}
}
// never let a process live forever
// always kill a process everytime we are done using it
componentWillUnmount() {
if (this.state.intervalIsSet) {
clearInterval(this.state.intervalIsSet);
this.setState({ intervalIsSet: null });
}
}
// just a note, here, in the front end, we use the id key of our data object
// in order to identify which we want to Update or delete.
// for our back end, we use the object id assigned by MongoDB to modify
// data base entries
// our first get method that uses our backend api to
// fetch data from our data base
getDataFromDb = () => {
fetch('http://localhost:3001/api/getData')
.then((data) => data.json())
.then((res) => this.setState({ data: res.data }));
};
I`d like it to compile
To solve your problem change this line getDataFromDb = () => { to getDataFromDb(){.
This is happening because you don't have class properties configured on your build configurations, a plugin for babel: https://babeljs.io/docs/en/babel-plugin-proposal-class-properties
Note that this syntax getDataFromDb = () => { is not approved yet, the proposal and more info on class properties: https://github.com/tc39/proposal-class-fields
Actually the problem is here,
let interval = setInterval(this.getDataFromDb, 1000);
You should call your getDataFromDb function like this,
let interval = setInterval(this.getDataFromDb(), 1000);
Also as you are using arrow function, no need to bind this,
this.getDataFromDb = this.getDataFromDb.bind(this); //You can remove this line
Demo.
Note:: Here fetch call changed to some other URL to get it work.
If you are using arrow function then there is no need to bind. So remove this.getDataFromDb = this.getDataFromDb.bind(this); this line it will work fine.
You forgot to complete closing bracket } at the end. If you are using arrow function then there is no need of bind so remove this line 'this.getDataFromDb = this.getDataFromDb.bind(this);'
Related
So at the moment I am having to put my request / api logic directly into my components because what I need to do a lot of the time is set state based on the response I get from the back end.
Below is a function that I have on my settings page that I use to save the settings to recoil after the user hits save on the form:
const setUserConfig = useSetRecoilState(userAtoms.userConfig);
const submitSettings = async (values: UserConfigInterface) => {
try {
const { data: {data} } = await updateUser(values);
setUserConfig({
...data
});
} catch (error) {
console.log('settings form error: ', error);
}
}
This works perfectly...I just dont want the function in my component as most of my components are getting way bigger than they need to be.
I have tried making a separate file to do this but I can only use the recoil hooks (in this instance useSetRecoilState) inside of components and it just complains when I try and do this outside of a react component.
I have tried implementing this with recoils selector and selectorFamily functions but it gets kind of complicated. Here is how I have tried it inside of a file that has atoms / selectors only:
export const languageProgress = atom<LanguageProgress>({
key: "LanguageProgress",
default: {
level: 1,
xp: 0,
max_xp: 0
}
})
export const languageProgressUpdate = selectorFamily<LanguageProgress>({
key: "LanguageProgress",
get: () => async () => {
try {
const { data: { data } } = await getLanguageProgress();
return data;
} catch (error) {
console.log('get language progress error');
}
},
set: (params:object) => async ({set}) => {
try {
const { data: { data } } = await updateLanguageProgress(params);
set(languageProgress, {
level: data.level,
xp: data.xp,
max_xp: data.max_xp
});
} catch (error) {
console.log('language progress update error: ', error);
}
}
});
What I want to do here is get the values I need from the back end and display it in the front which I can do in the selector function get but now I have 2 points of truth for this...my languageProgress atom will initially be incorrect as its not getting anything from the database so I have to use useGetRevoilValue on the languageProgressUpdate selector I have made but then when I want to update I am updating the atom and not the actual value.
I cannot find a good example anywhere that does what I am trying to here (very suprisingly as I would have thought it is quite a common way to do things...get data from back end and set it in state.) and I can't figure out a way to do it without doing it in the component (as in the first example). Ideally I would like something like the first example but outside of a component because that solution is super simple and works for me.
So I dont know if this is the best answer but it does work and ultimately what I wanted to do was seperate the logic from the screen component.
The answer in my situation is a bit long winded but this is what I used to solve the problem: https://medium.com/geekculture/crud-with-recoiljs-and-remote-api-e36581b77168
Essentially the answer is to put all the logic into a hook and get state from the api and set it there.
get data from back end and set it in state
You may be looking for useRecoilValueLoadable:
"This hook is intended to be used for reading the value of asynchronous selectors. This hook will subscribe the component to the given state."
Here's a quick demonstration of how I've previously used it. To quickly summarise: you pass useRecoilValueLoadable a selector (that you've defined somewhere outside the logic of the component), that selector grabs the data from your API, and that all gets fed back via useRecoilValueLoadable as an array of 1) the current state of the value returned, and 2) the content of that API call.
Note: in this example I'm passing an array of values to the selector each of which makes a separate API call.
App.js
const { state, contents } = useRecoilValueLoadable(myQuery(arr));
if (state.hasValue && contents.length) {
// `map` over the contents
}
selector.js
import { selectorFamily } from 'recoil';
export const myQuery = selectorFamily({
key: 'myQuery',
get: arr => async () => {
const promises = arr.map(async item => {
try {
const response = await fetch(`/endpoint/${item.id}`);
if (response.ok) return response.json();
throw Error('API request not fulfilled');
} catch (err) {
console.log(err);
}
});
const items = await Promise.all(promises);
return items;
}
});
I have a MobX store where I have a function doing an API call. It works fine it's getting the data but it doesn't update the already rendered page. I'm following this tutorial https://medium.com/#borisdedejski/next-js-mobx-and-typescript-boilerplate-for-beginners-9e28ac190f7d
My store looks like this
const isServer = typeof window === "undefined";
enableStaticRendering(isServer);
interface SerializedStore {
PageTitle: string;
content: string;
isOpen: boolean;
companiesDto: CompanyDto[],
companyCats: string[]
};
export class AwardStore {
PageTitle: string = 'Client Experience Awards';
companiesDto : CompanyDto[] = [];
companyCats: string[] = [];
loadingInitial: boolean = true
constructor() {
makeAutoObservable(this)
}
hydrate(serializedStore: SerializedStore) {
this.PageTitle = serializedStore.PageTitle != null ? serializedStore.PageTitle : "Client Experience Awards";
this.companyCats = serializedStore.companyCats != null ? serializedStore.companyCats : [];
this.companiesDto = serializedStore.companiesDto != null ? serializedStore.companiesDto : [];
}
changeTitle = (newTitle: string) => {
this.PageTitle = newTitle;
}
loadCompanies = async () => {
this.setLoadingInitial(true);
axios.get<CompanyDto[]>('MyAPICall')
.then((response) => {
runInAction(() => {
this.companiesDto = response.data.sort((a, b) => a.name.localeCompare(b.name));
response.data.map((company : CompanyDto) => {
if (company.categories !== null ) {
company.categories?.forEach(cat => {
this.addNewCateogry(cat)
})
}
})
console.log(this.companyCats);
this.setLoadingInitial(false);
})
})
.catch(errors => {
this.setLoadingInitial(false);
console.log('There was an error getting the data: ' + errors);
})
}
addNewCateogry = (cat : string) => {
this.companyCats.push(cat);
}
setLoadingInitial = (state: boolean) => {
this.loadingInitial = state;
}
}
export async function fetchInitialStoreState() {
// You can do anything to fetch initial store state
return {};
}
I'm trying to call the loadcompanies from the _app.js file. It calls it and I can see in the console.log the companies etc but the state doesn't update and I don't get to see the actual result. Here's the _app.js
class MyApp extends App {
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = {
awardStore: new AwardStore()
};
this.state.awardStore.loadCompanies();
}
// Fetching serialized(JSON) store state
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext);
const initialStoreState = await fetchInitialStoreState();
return {
...appProps,
initialStoreState
};
}
// Hydrate serialized state to store
static getDerivedStateFromProps(props, state) {
state.awardStore.hydrate(props.initialStoreState);
return state;
}
render() {
const { Component, pageProps } = this.props;
return (
<Provider awardStore={this.state.awardStore}>
<Component {...pageProps} />
</Provider>
);
}
}
export default MyApp;
In the console.log I can see that this.companyCat is update but nothing is changed in the browser. Any ideas how I can do this? Thank you!
When you do SSR you can't load data through the constructor of the store because:
It's does not handle async stuff, so you can't really wait until the data is loaded
Store is created both on the server side and on the client too, so if theoretically constructor could work with async then it still would not make sense to do it here because it would load data twice, and with SSR you generally want to avoid this kind of situations, you want to load data once and reuse data, that was fetched on the server, on the client.
With Next.js the flow is quite simple:
On the server you load all the data that is needed, in your case it's loaded on the App level, but maybe in the future you might want to have loader for each page to load data more granularly. Overall it does not change the flow though
Once the data is loaded (through getInitialProps method or any other Next.js data fetching methods), you hydrate your stores and render the application on the server side and send html to the client, that's SSR
On the client the app is initialized again, though this time you don't want to load the data, but use the data which server already fetched and used. This data is provided through props to your page component (or in this case App component). So you grab the data and just hydrate the store (in this case it's done with getDerivedStateFromProps).
Based on that, everything you want to fetch should happen inside getInitialProps. And you already have fetchInitialStoreState method for that, so all you need to do is remove data fetching from store constructor and move it to fetchInitialStoreState and only return the data from it. This data will then go to the hydrate method of your store.
I've made a quick reproduction of your code here:
The huge downside if App.getInitialProps is that it runs on every page navigation, which is probably not what you want to do. I've added console.log("api call") and you can see in the console that it is logged every time you navigate to any other page, so the api will be called every time too, but you already have the data so it's kinda useless. So I recommend in the future to use more granular way of loading data, for example with Next.js getServerSideProps function instead (docs).
But the general flow won't change much anyway!
Calling awardStore.loadCompanies in the constructor of MyApp is problematic because the loadCompanies method is populating the store class. What you want is to hydrate the store with the companyCats data. Since server and client stores are distinct, you want to load the data you need on the server side i.e. fetchInitialStoreState (or load it from a page's getStaticProps/getServerSideProps method) so that you can pass it into the hydrate store method from page/app props.
Note loadCompanies is async so it'll be [] when getDerivedStateFromProps is called so there's nothing to hydrate. For your existing hydrate method to work you need initialStoreState to be something like the fetchInitialStoreState method below. Alternatively if it's fetched on the page level, the hydrate may be closer to initialData?.pageProps?.companyCats
It's common to see the store hydration as needed for each page though it's still valid to call loadCompanies() from the client side. There's a lot I didn't get a chance to touch on but hopefully this was somewhat helpful.
export const fetchInitialStoreState = async() => {
let companyCats = [];
try {
const response = await axios.get < CompanyDto[] > ('MyAPICall')
response.data.map((company: CompanyDto) => {
if (Array.isArray(company.categories) && company.categories.length > 0) {
companyCats.push(...company.categories)
}
})
} catch (error) {
// Uh oh...
}
return {
serializedStore: {
companyCats,
// PageTitle/etc
}
}
}
I need to fetch the user data and display it. I am getting an error now that says
TypeError: this.unsubscribe is not a function
and when I initialise it as a normal variable like const db, then I get another error
Function CollectionReference.doc() requires its first argument to be of type non-empty string
import React from "react";
import { auth, firestore } from "../../firebase/firebase.utils";
export default class UserPage extends React.Component {
state = {
user: {}
};
unsubscribe = null;
componentDidMount() {
const user = auth.currentUser;
this.unsubscribe = firestore
.collection("users")
.doc(user)
.onSnapshot(doc => {
this.setState = {
user: doc.data()
};
});
}
componentWillMount() {
this.unsubscribe();
}
render() {
return (
<div>
<h1>{this.state.user.name}</h1>
</div>
);
}
}
when I insinalise it as a normal variable like const db
Not quite sure what you mean by this, but if you're getting an error about the type of unsubscribe, I suggest using console.log right before you call it to view its value.
Bear in mind that componentWillMount happens in the lifecycle before componentDidMount (hence the names will and did). I suspect that's one of your problems: you try to call unsubscribe before setting the value.
With regard to your other error about the doc call, it's likely referring to:
...
.collection("users")
.doc(user) <-- this line
.onSnapshot(doc => {
this.setState = {
us
...
As the error output states, that user variable (the first argument of doc) must be a string, and it can't be an empty string.
I don't see user anywhere in your code, so I expect that it's currently the value undefined. You could access this.state.user here, but I'd strongly advise against it since you subsequently set that state in the call (probably cause an infinite loop).
What is your end goal? What have you tried to resolve these two issues? Maybe adding that to your question would help us assist you better.
I am trying to make a 'facebook kind of newsfeed' in React with Firebase Firestore. In the componentDidMount I first get the friendslist and per friend I will get their activities which I push to an empty array and sort() + reverse() the id's which are timestamps. This way the newest activity will be first in the array. Once ALL the items are pushed to the array, I want to set the state with the array. This is the code that I have:
import React, { Component } from 'react'
import { db, firebaseAuth } from '../../helpers/base'
import Activities from './Activities'
export default class ActivityList extends Component {
state = {
activityKeys: [],
}
componentDidMount(){
const uid = firebaseAuth().currentUser.uid
var activityKeys = []
db.doc(`users/${uid}/social/friends`).get().then( (doc) => {
//GET ALL THE FRIENDS
const friends = doc.data() //OBJECT OF {friendOneId: "friendOneId", friendTwoId: "friendTwoId"}
//LOOP THROUGH FRIENDS AND GET ACTIVITY
Object.keys(friends).forEach( friend => {
db.collection("activity").where("user", "==", friend).get().then((querySnapshot) => {
querySnapshot.forEach( (doc) => {
const activity = doc.id
activityKeys.push(activity)
activityKeys.sort().reverse()
})
})
})
})
console.log('activityKeys: ', activityKeys)
this.setState({ activityKeys })
}
render () {
return (
<div>
<h5>Activity List</h5>
<Activities activityKeys={this.state.activityKeys} />
</div>
)
}
}
The problem is that the array isn't set correctly or that it maybe is set before all the items are pushed. This is the log that I get:
It looks like it is loaded but it is empty between the brackets. If I console.log This.state.activity I get the same result. Can someone tell me how to fix this? And how can I setState once all the activities are pushed to the empty array?
You get a Promise when you make a call to API, but you set this.setState({ activityKeys }) before the call has completed. In other words, you must chain another .then() after the data has been received, in which you will call this.setState({ activityKeys }). What makes it a little difficult is that you're creating many Promises when iterating with forEach, and you need to wait for each of them to complete. You could save them all to list, and use Promise.all to wait for their completion and return it from the previous .then. Read more on the promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
I have App.js file and it is root of my application (both ios and android reference to it).
I have a value that I keep in AsyncStorage that I need before app.js render method is called.
Problem is that as it is async it late and I can't get that value.
class App extends React.Component {
constructor(props) {
super(props);
this.init()
}
async init() {
try {
const value = await AsyncStorage.getItem('#myPoorValue:key');
if (value !== null){
...
}
} catch (error) {}
}
}
...
render (...
I hope that I explained good what is my issue here.
I know that there is no way to get it synchronous (I would like that) but don't know what to do in this situation.
To explain it a bit better I use I18n and I manually set I18n.locale to some value and other components get default value before I set it manually.
Just to note I also use redux and I pass selected value to it.
try the following:
...
constructor(props) {
super(props)
this state = {
isLoading: true
}
}
async componentDidMount() {
await this.init()
// you might want to do the I18N setup here
this.setState({
isLoading: false
})
}
async init() {
const value = await AsyncStorage.getItem('#myPoorValue:key')
...
}
...
the thing is that init() returns a promise and you need to wait until it gets resolved. That's when await comes to rescue.
you'll also need to set up some loader that will be there on first render, and toggle the state to replace it with actual markup after the AsyncStorage value has been fetched. I've put it in the code, but you might want to trigger a redux action instead, depending on your setup.