React - get request using axios not able to setState - javascript

I am new to react and currently working on a project.
I usually use Class Components, however, I am trying to learn Functional Components but I encounter an issue with the setState.
My Component:
function GetCustomerDetailsFc(this: any, props: CustomerModel): JSX.Element {
const [state, setState] = useState('');
async function send(){
try {
const response = await axios.get<CustomerModel>(globals.urls.customerDetails)
store.dispatch(oneCustomerAction(response.data));
console.log(response.data)
setState( {customer: response.data});
} catch (err) {
notify.error(err);
}
}
useEffect(() => {
send();
});
return (
<><h1>{this.state.customer.email}</h1></>
);
}
export default GetCustomerDetailsFc;
I am not sure on how to save state so I can read it.
when I log the request I can see the back-end actually returns data which contains:
User details : (id, email, password , etc...)
An Array of Coupons linked to users purchase.
so basically I can see the request responds properly, however, since I am new I am not sure exactly about the syntax and how I could read properly the data.
Customer Model:
class CustomerModel {
public id: number;
public firstName :string;
public lastName :string;
public email: string;
public token: string;
public password: string;
}
export default CustomerModel;
Note:: I use redux for this project and store data accordingly.
Thanks.

If the returned data is intended to be used locally in this component, then what you pass into useState should match the response you are receiving from the server.
So if for instance your server returns a CostomerModel object, you should initiate your useState with an object -
const [ customer, setCustomer ] = useState({})
try{
const response = await axios.get<CustomerModel>(globals.urls.customerDetails)
...
setCustomer(response);
...
}

Related

Update Profile During SignUp Angular Firebase

I am using angularfire/firebase for an Angular project where I am working through authentication.
When a user signs up, the following function is called:
signup(email: string, password: string, name: string) {
return this.fireAuth
.createUserWithEmailAndPassword(email, password)
.then((res) => {
res.user?.updateProfile({
displayName: name,
});
// set vars and local storage
});
}
In my app.component, I have the following subscription to track changes and store in my state management (I don't think StateMgmt is the issue here):
ngOnInit() {
this.fireAuth.authState.user.subscribe((res) => {
if(res) {
console.log(res);
console.log(res.displayName);
this.store.dispatch(new SetUser(res));
}
}
When a user signs up, the name they enter upon signup is set through the res.user.updateProfile(... function, and since the authState had a change, the subscription in the app.component prints out an object and stores to the SetUser(res) state.
The console.log() from here in the app state prints out a large object, including the email, uid, and displayName. This is then stored in my state object. However, on theconsole.log(res.displayName), the result is null until I refresh the page triggering an "auto logon" where the displayName is fetched from firebase.
It seems to me the object of the .then((res)... from my signUp() updateProfile is not being triggered until after the user is already changed and stored.
Is there a way to make this signUp function work so that the SetUser(res) is only dispatched once the updateProfile({... is complete and the displayName is no longer null?
From my app.component console.log(res):
{
"uid": "vbn6TA8vTiLi7J2",
"displayName": "my name",
"photoURL": null,
"email": "email#email.com",
...
}
app.component console.log(res.displayName):
null
EDIT
Another interesting thing I found is that when I take a copy of the res and print it out, the displayName is also null.
const r = JSON.parse(JSON.stringify(res));
console.log(res);
console.log(r);
the console.log(r) has an object where the displayName is null. The console.log(res) has an object where the displayName is NOT null and is the name passed in to the signUp(... function.
This maybe a race condition within Angular Fire, I would ensure that the user is finalized before updating the user.
async UpdateProfile(displayName: string) {
const profile = {
displayName: stored.username,
photoURL: stored.profile,
email: stored.email,
}
return (await this.afAuth.currentUser).updateProfile(profile);
}
I am making the assumption you are storing the user details via a form object as stored but this can easily be from the auth provider or a custom object
Even waiting for the user to be finalized via async await before moving on did not work.
What I ended up doing was the following:
In my signUp() function, once the user was created, I retrieved the current use via this.fireAuth.authState (fireAuth is from AngularFireAuth in my constructor). I subscribed to this, while piping to only take the first value, and then dispatched my SetUser function. This ensures that the function is only running after the updateProfile is complete using a .then().
Because I moved the state dispatch into my signup function, I removed the subscription from my AppComponent.
Here is my signup function from the auth service:
...
constructor(
public fireAuth: AngularFireAuth,
) {
this.user = this.fireAuth.authState;
}
signup(email: string, password: string, name: string) {
return this.fireAuth
.createUserWithEmailAndPassword(email, password)
.then((res) => {
res.user
?.updateProfile({
displayName: name,
})
.then(() => {
this.user.pipe(first()).subscribe((res) => {
this.store.dispatch(new SetUser(res));
});
});
});
}

React Native API data fetch and save as global data

I am new to React Native. I am creating a simple app that has a Drawer Navigation. After login I want to update user Name and Email on the Drawer. I tried to fetch the the data from API using fetch() in the ComponentDidMount() method, but the problem is when I am setting the data in state it has some promise error.
I want to know the better approach to fetch the data from API and store it so that it can be accessible in any component, usually the auth token and email of the user.
Also anyone suggest a better way to maintain the code with API fetching, I am writing the code for fetch in every component so it become cluttered everywhere.
Update Code:
export class CustomDrawer extends React.Component{
state = {
token: '',
id: '',
user: '',
}
componentDidMount = async() =>{
await this.getAll();
}
setMyToken = () => {
AsyncStorage.getItem('authToken').then((value) =>{
let temp = JSON.parse(value)
this.setState({token : temp.token})
}
getAll = async() => {
await this.setMyToken();
return fetch(`API_URL`).then(response=>response.text().then(text=>{
const data = text && JSON.parse(text)
this.setState({user: data.username, id: data.id})
})).catch(function(error){
console.log(error)
});
}
}

Problems accessing data from a GraphQL query in Javascript with Svelte

Below I have my working function using a normal REST response that I want to try to convert into a GraphQL version, it fetches a JSON doc from my Phoenix Server and stores the object values from the JSON doc into an object. The problem is, here I can use await and then assign the new object values from the object within the JSON document, but using GraphQL I cannot access this data to assign it because there is no await function as its just a Query. (From what I know)
async function updatePageWithNewCompany(company){
const res = await fetch(`http://localhost:4000/${company}`);
profile = await res.json();
profile = profile.companyProfile
DashboardStore.update(currentData => {
return {
id: profile.id,
companyName: `${profile.company_name}`,
averageLength: profile.average_length,
}
})
Ultimately I am asking if there is a way to access and assign data from a GraphQL query in JavaScript so I can manipulate it before displaying it in my frontend Svelte app.
Example of current GraphQL query:
import { gql } from '#apollo/client'
import { client } from './apollo';
import { query, setClient } from "svelte-apollo";
setClient(client)
const GET_COMPANY = gql`
query{
companyProfile(){
companyName
averageLength
}
}
`;
const response = query(GET_COMPANY)
...
svelte-apollo queries return
a svelte store of promises that resolve as values come in
as stated in the example query displayed in the documentation.
As such, you can exploit that format directly in the script section. Here is an example:
...
async function getCompany() {
const companyStore = query(GET_COMPANY)
const company = (await $companyStore).data.companyProfile
return company // format will be an object with keys { companyName, averageLength } according to your query
}
...
On a side-note I would recommend always getting the id of objects in your GraphQL queries as it is usually the key used internally by the Apollo client to manage its cache (unless explicitly stated otherwise).
I have found a working solution with the help of #Thomas-Hennes
import { client } from '../apollo.js';
import { onMount } from "svelte";
import { gql } from '#apollo/client'
export const COMPANY_LIST = gql`
query {
listCompanies{
companyName
}
}
`;
async function listCompanies() {
await client.query({query: COMPANY_LIST})
.then(res => {
companyList.update( currentData =>
[...res.data.listCompanies.companyName])})
};
onMount(() =>{
listCompanies()
})
await didn't like being assigned to a variable or having its data manipulated so i used it as a promise instead and manipulated the response.
The below link helped me find the final piece of the puzzle.
https://docs.realm.io/sync/graphql-web-access/using-the-graphql-client

Does Apollo cache the returned data from a mutation

I'm using Apollo Client in a React app and I need to do a mutation then keep the returned data for later use (but I won't have access to the variables ), do I have to use a another state management solution or can we do this in Apollo?
I've read about doing this with query but not mutation.
Here's my code so far
// Mutation
const [myMutation, { data, errors, loading }] = useMutation(MY_MUTATION, {
onCompleted({ myMutation }) {
console.log('myMutation: ', myMutation.dataToKeep);
if (myMutation && myMutation.dataToKeep)
SetResponse(myMutation.dataToKeep);
},
onError(error) {
console.error('error: ', error);
},
});
//How I call it
onClick={() => {
myMutation({
variables: {
input: {
phoneNumber: '0000000000',
id: '0000',
},
},
});
}}
edit:
here is the mutation
export const MY_MUTATION = gql`
mutation MyMutation($input: MyMutationInput!) {
myMutation(input: $input) {
dataToKeep
expiresAt
}
}
`;
and the schema for this mutation
MyMutationInput:
phoneNumber: String!
id: String!
MyMutationPayload:
dataToKeep
expiresAt
Case 1: Payload is using common entities
Simply put, the Apollo client's cache keeps everything that's received from queries and mutations, though the schema needs to include id: ID! fields and any query needs to use both the id and __typename fields on relevant nodes for the client to know which part of the cache to update.
This assumes that the mutation payload is common data from the schema that can be retrieved through a normal query. This is the best case scenario.
Given the following schema on the server:
type User {
id: ID!
phoneNumber: String!
}
type Query {
user(id: String!): User!
}
type UpdateUserPayload {
user: User!
}
type Mutation {
updateUser(id: String!, phoneNumber: String!): UpdateUserPayload!
}
And assuming a cache is used on the client:
import { InMemoryCache, ApolloClient } from '#apollo/client';
const client = new ApolloClient({
// ...other arguments...
cache: new InMemoryCache(options)
});
The cache generates a unique ID for every identifiable object included in the response.
The cache stores the objects by ID in a flat lookup table.
Whenever an incoming object is stored with the same ID as an existing object, the fields of those objects are merged.
If the incoming object and the existing object share any fields, the incoming object overwrites the cached values for those fields.
Fields that appear in only the existing object or only the incoming object are preserved.
Normalization constructs a partial copy of your data graph on your
client, in a format that's optimized for reading and updating the
graph as your application changes state.
The client's mutation should be
mutation UpdateUserPhone($phoneNumber: String!, $id: String!) {
updateUser(id: $id, phoneNumber: $phoneNumber) {
user {
__typename # Added by default by the Apollo client
id # Required to identify the user in the cache
phoneNumber # Field that'll be updated in the cache
}
}
}
Then, any component using this user through the same Apollo client in the app will be up to date automatically. There's nothing special to do, the client will use the cache by default and trigger renders whenever the data changes.
import { gql, useQuery } from '#apollo/client';
const USER_QUERY = gql`
query GetUser($id: String!) {
user(id: $id) {
__typename
id
phoneNumber
}
}
`;
const UserComponent = ({ userId }) => {
const { loading, error, data } = useQuery(USER_QUERY, {
variables: { id: userId },
});
if (loading) return null;
if (error) return `Error! ${error}`;
return <div>{data.user.phoneNumber}</div>;
}
The fetchPolicy option defaults to cache-first.
Case 2: Payload is custom data specific to the mutation
If the data is in fact not available elsewhere in the schema, it won't be possible to use the Apollo cache automatically as explained above.
Use another state management solution
A couple options:
local storage
React's context API
etc.
Here's an example from the Apollo GraphQL documentation using the localStorage:
const [login, { loading, error }] = useMutation(LOGIN_USER, {
onCompleted({ login }) {
localStorage.setItem('token', login.token);
localStorage.setItem('userId', login.id);
}
});
Define a client-side schema
This is a pure Apollo GraphQL solution since the client is also a state management library, which enables useful developer tooling and helps reason about the data.
Create a local schema.
// schema.js
export const typeDefs = gql`
type DataToKeep {
# anything here
}
extend type Query {
dataToKeep: DataToKeep # probably nullable?
}
`;
Initialize a custom cache
// cache.js
export const dataToKeepVar = makeVar(null);
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
dataToKeep: {
read() {
return dataToKeepVar();
}
},
}
}
}
});
Apply the schema override at the client's initialization
import { InMemoryCache, Reference, makeVar } from '#apollo/client';
import { cache } from './cache';
import { typeDefs } from './schema';
const client = new ApolloClient({
cache,
typeDefs,
// other options like, headers, uri, etc.
});
Keep track of the changes in the mutation:
const [myMutation, { data, errors, loading }] = useMutation(MY_MUTATION, {
onCompleted({ myMutation }) {
if (myMutation && myMutation.dataToKeep)
dataToKeepVar(myMutation.dataToKeep);
}
});
Then, query the #client field.
import { gql, useQuery } from '#apollo/client';
const DATA_QUERY = gql`
query dataToKeep {
dataToKeep #client {
# anything here
}
}
`;
const AnyComponent = ({ userId }) => {
const { loading, error, data } = useQuery(DATA_QUERY);
if (loading) return null;
if (error) return `Error! ${error}`;
return <div>{JSON.stringify(data.dataToKeep)}</div>;
}
See also the documentation on managing local state.

Firestore Function DocumentReference.update() called with invalid data. Unsupported field value: a custom object

I'm following Firebase's instructions and my functions is as follows:
import { DataSource, DataSourceConfig } from "apollo-datasource";
import { KeyValueCache } from "apollo-server-caching";
import firebase from "firebase";
import admin from "firebase-admin";
import "#firebase/firestore";
import { Request } from "apollo-server-env";
export class FirebaseDataSource<TContext = any> extends DataSource {
context!: TContext;
db: firebase.firestore.Firestore;
constructor({
firebaseConfig,
serviceAccount,
databaseURL,
}: {
serviceAccount: any;
firebaseConfig: firebase.app.App;
databaseURL: string;
}) {
super();
this.context;
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL,
});
}
if (!this.db) {
this.db = firebase.firestore();
}
}
async initialize(config: DataSourceConfig<TContext & { request: Request }>) {
this.context = config.context;
}
async user_updateRestaurantFavorites(data: {
uid: string;
someId: string;
add: boolean;
}) {
const collectionRef = this.db.collection("users");
const documentRef = collectionRef.doc(data.uid);
let favorites;
if (data.add) {
favorites = await documentRef.update({
favorites: admin.firestore.FieldValue.arrayUnion(
data.someId
),
});
} else {
favorites = await documentRef.update({
favorites: admin.firestore.FieldValue.arrayRemove(
data.someId
),
});
}
return favorites;
}
}
export default FirebaseDataSource;
I dubugged it and I do pass the uid, add, and someId correctly.
someId is a string and add is a boolean (true)
When I run this, I get:
Firestore Function DocumentReference.update() called with invalid data. Unsupported field value: a custom object (found in field favorites in document users/XXXXXXX)
I am just running their own function with a simple string.
Below is an image of my firestore showing the user record does indeed have an empty array ready to accept strings
What am I doing wrong?
You're mixing up the web client and admin client SDKs. You can't use the FieldValue objects exported by firebase-admin when calling methods exported by firebase. The error message is coming from the web client SDK, and it's effectively telling you that you passed an object that it doesn't understand (from the Admin SDK).
You should pick one or the other, and completely remove the one you aren't using in order to avoid problems. If this runs on a backend, you should only use the Firebase Admin SDK, and skip the web client altogether. If you do this, you will need to assign this.db using the admin SDK, probably as this.db = admin.firestore().
Firebase can only store primitive types, maps and array of same. In your case you are saving the result of admin.firestore.FieldValue.arrayUnion(...) for the property favorites.
My guess is that the result is not returning a supported type. I have not used FieldValue before ... is that the correct way to use the API?
It simply means that you need to send exact data which was received by the query. Partial object not allowed
db.collection("users").where("name", "==", somename).limit(1).get().then(query => {
console.log(query);
const thing = query.docs[0];
console.log(thing.data());
let tmp = thing.data();
tmp.current_game_play = tmp.current_game_play + 1;
console.log(tmp);
thing.ref.update(tmp);
});

Categories

Resources