Calling .subscribe() after .pipe(); not executing my redirection code - javascript

I have an AuthService with these methods:
signUp = (data: SignUp): Observable<AuthResponseData> => {
const endpoint = `${env.authBaseUrl}:signUp?key=${env.firebaseKey}`;
return this._signInOrSignUp(endpoint, data);
};
signIn = (data: SignIn): Observable<AuthResponseData> => {
const endpoint = `${env.authBaseUrl}:signInWithPassword?key=${env.firebaseKey}`;
return this._signInOrSignUp(endpoint, data);
};
private _signInOrSignUp = (endpoint: string, data: SignIn | SignUp): Observable<AuthResponseData> => {
return this.http.post<AuthResponseData>(endpoint, {
...data,
returnSecureToken: true
}).pipe(
catchError(error => this._throwError(error)),
tap(response => this._createAndEmitUserSubject(response)),
);
}
private _throwError = error => {
const errorMessage = error.error.error.message;
return throwError(errorMessage);
};
private _createAndEmitUserSubject = (response: AuthResponseData) => {
const expirationDate = new Date(new Date().getTime() + +response.expiresIn * 1000);
const user = new User(
response.idToken,
response.email,
response.idToken,
expirationDate
);
localStorage.setItem("user", JSON.stringify(user));
this.user$.next(user);
};
And in the sign-in and sign-up components, I call the those in following way:
submit = (): void => {
if (this.loginForm.invalid) {
return;
}
this.authService
.signIn(this.loginForm.value)
.subscribe({
next: () => this.router.navigate(["/recipes"]),
error: error => this.error = error
});
};
But the code within next is not executed. If I remove .pipe() it is. I was hoping I did not need to use .pipe() in two places.

Looks like this._throwError doesn't throw an error. If it was, next couldn't be called later. You are probably not throw'ing error in it and instead returning some value that goes into next

Removing this.user$.next(user) solves it, and I did't realize I did not need this at all, but I'd like to know why it blocked the flow.
private _createAndEmitUserSubject = (response: AuthResponseData) => {
// ...
localStorage.setItem("user", JSON.stringify(user));
};

Related

How to update RTK Query cache when Firebase RTDB change event fired (update, write, create, delete)

I am using redux-tookit, rtk-query (for querying other api's and not just Firebase) and Firebase (for authentication and db).
The code below works just fine for retrieving and caching the data but I wish to take advantage of both rtk-query caching as well as Firebase event subscribing, so that when ever a change is made in the DB (from any source even directly in firebase console) the cache is updated.
I have tried both updateQueryCache and invalidateTags but so far I am not able to find an ideal approach that works.
Any assistance in pointing me in the right direction would be greatly appreciated.
// firebase.ts
export const onRead = (
collection: string,
callback: (snapshort: DataSnapshot) => void,
options: ListenOptions = { onlyOnce: false }
) => onValue(ref(db, collection), callback, options);
export async function getCollection<T>(
collection: string,
onlyOnce: boolean = false
): Promise<T> {
let timeout: NodeJS.Timeout;
return new Promise<T>((resolve, reject) => {
timeout = setTimeout(() => reject('Request timed out!'), ASYNC_TIMEOUT);
onRead(collection, (snapshot) => resolve(snapshot.val()), { onlyOnce });
}).finally(() => clearTimeout(timeout));
}
// awards.ts
const awards = dbApi
.enhanceEndpoints({ addTagTypes: ['Themes'] })
.injectEndpoints({
endpoints: (builder) => ({
getThemes: builder.query<ThemeData[], void>({
async queryFn(arg, api) {
try {
const { auth } = api.getState() as RootState;
const programme = auth.user?.unit.guidingProgramme!;
const path = `/themes/${programme}`;
const themes = await getCollection<ThemeData[]>(path, true);
return { data: themes };
} catch (error) {
return { error: error as FirebaseError };
}
},
providesTags: ['Themes'],
keepUnusedDataFor: 1000 * 60
}),
getTheme: builder.query<ThemeData, string | undefined>({
async queryFn(slug, api) {
try {
const initiate = awards.endpoints.getThemes.initiate;
const getThemes = api.dispatch(initiate());
const { data } = (await getThemes) as ApiResponse<ThemeData[]>;
const name = slug
?.split('-')
.map(
(value) =>
value.substring(0, 1).toUpperCase() +
value.substring(1).toLowerCase()
)
.join(' ');
return { data: data?.find((theme) => theme.name === name) };
} catch (error) {
return { error: error as FirebaseError };
}
},
keepUnusedDataFor: 0
})
})
});

Why does my update in a put request overwrite the whole record in Sequelize?

I'm trying to make a "edit" feature for my project, and I'm stuck at this part..
I have a put request :
export const updateEvent = (event, id) => (dispatch, getState) => {
request
.put(`${baseUrl}/event/${id}`)
.send(event)
.then(response => {
dispatch(updatedEvent(response.body))
})
.catch(error => console.log(error))
}
This is the route for the said put, with Sequelize as ORM:
router.put('/event/:id', async (req, res, next) => {
const { id } = req.params
try {
const event = await Event.findByPk(id)
const updatedEvent = await event.update(req.body)
res.send(updatedEvent)
} catch (error) {
next(error)
}
})
When I test it with postman, everything works as expected. Where I ran into my problem is when I'm sending the put data from React in the frontend.
I have a form, and I save my data in the local state, and then dispatch it to actions like this:
handleSubmit = e => {
e.preventDefault()
const id = this.props.event.id
const updatedEvent = {
name: this.state.name,
description: this.state.description,
picture: this.state.picture,
startDate: this.state.startDate,
endDate: this.state.endDate,
userId: this.props.userId
}
this.props.updateEvent(updatedEvent, id)
}
Any value that is left empty in the form is overwriting my fields with nothing (an empty string). How do I properly handle this?
A solution is to filter your object, such that you remove any properties which have empty values and therefore won't be included in the database update.
In your router.put():
router.put('/event/:id', async (req, res, next) => {
const { id } = req.params
try {
const event = await Event.findByPk(id);
// filter req.body to remove empty values
const { body } = req;
const filteredBody = Object.keys(body).reduce((resultObj, key) => {
if(body[key] != ''){
resultObj[key] = body[key];
}
return resultObj;
}, {});
const updatedEvent = await event.update(filteredBody);
res.send(updatedEvent)
} catch (error) {
next(error)
}
})

Converting from class to functional component with async state setting

I have a simple class-based component that I'm trying to convert to a function-based component, but am running into all kinds of dead ends.
My component is a straightforward adaptation of the boilerplate gifted-chat package, and uses Watson Assistant as a backend to provide responses. There's nothing complex about the backend part, these are just thin wrappers on Watson Assistants's API:
getSessionID = async (): Promise<string>
gets a session ID for use in communicating with the backend, and
sendReply = async (reply: string, sessionID: string): Promise<string>
returns Assistant's response to the string provided as a reply. These are not the source of the trouble I'm having (the bodies of both could be replaced with return await "some string" and I'd have the same issues): the class-based version (below) works perfectly.
But I'm at a loss to figure out how to convert this to a functional form, in particular:
I'm struggling to find a suitable replacement for componentWillMount. Using useEffect with sessionID as state results in errors: getMessage gets called (even if I await) before the required sessionID is set.
I can avoid this by not making sessionID state (which it arguably shouldn't be) and just making it a global (as in the functional attempt below). But even if I do this:
After each user reply, and receipt of a response, the user reply is removed from the conversation, so that the entire conversation just consists of generated replies.
Both of these problems are, I think, linked to the lack of callbacks in the hook-based state setting idiom, but the issue could also lie elsewhere. In any case, I'm at a loss to know what to do.
Chatter.tsx (working class based version)
import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
class Chatter extends React.Component {
state = {
messages: [],
sessionID: null,
}
componentWillMount() {
WatsonAssistant.getSessionID()
.then((sID) => {
this.setState( {
sessionID: sID,
} )
} )
.then(() => this.getMessage(''))
.catch((error) => {
console.error(error)
} )
}
onSend = (message = []): void => {
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ), () => {
this.getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
} )
}
getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, this.state.sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ))
}
render() {
return (
<GiftedChat
messages={ this.state.messages }
onSend={ messages => this.onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
}
export default Chatter
Chatter.tsx (failed function based attempt)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage(''))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages)
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
}
const getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(messages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Chatter.tsx (working function based version)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage('', []))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages) // Apparently, no waiting goes on here
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '), newMessages)
}
const getMessage = async (text: string, currentMessages): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(currentMessages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Ok, since I don't have your full code I'm not sure this will just work as-is (in particular without the types from your dependencies I'm not sure if/how much the compiler will complain), but should give you something you can adapt easily enough.
const reducer = ({ messages }, action) => {
switch (action.type) {
case 'add message':
return {
messages: GiftedChat.append(messages, action.message),
};
case 'add sent message':
return {
// Not sure if .append is variadic, may need to adapt
messages: GiftedChat.append(messages, action.message, action.message[0].text.replace(/[\n\r]+/g, ' ')),
}
}
};
const Chatter = () => {
const [sessionID, setSessionID] = useState(null);
const [messages, dispatch] = useReducer(reducer, []);
const getMessage = async (text: string, sessionID: number, type: string = 'add message'): Promise<void> => {
const response = await WatsonAssistant.sendReply(text, sessionID);
const message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
};
dispatch({
type,
message,
});
};
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => (setSessionID(sID), sID))
.then(sID => getMessage('', sID))
.catch((error) => {
console.error(error)
});
}
fetchData();
}, []);
return (
<GiftedChat
messages={messages}
onSend={messages => getMessage(messages, sessionID, 'add sent message')}
user={{
_id: 1,
}}
/>
);
};
Main difference is useReducer. As far as I can tell in the original code you had two actions: append this message or append this message and then a copy of it with the text regex replaced. I've used different dispatches to the reducer to handle the cases rather than the callback to setState. I've modified your attempt at useEffect, here I'm (ab)using the comma operator to return the ID returned from the service so that it can be fed directly to getMessage as a parameter rather than relying on state that hasn't been updated yet.
I'm still kinda skeptical in general about the hooks API, but assuming this works I actually think it simplifies the code here.

Angular use of $timeout

I asked question here, I am still struggling. So after googling a lot, I am thinking of using $timeout. I am open to any other suggestion.
My final goal is to show alert message to user ONLY after updating the count on the screen.
I am not getting how to use $timeout. I am getting error:
Argument of type '(res: any, $timeout: any) => void' is not assignable to parameter of type '(value: Inspection) => void | PromiseLike'
I have changed the code a little after I posted my other question. I have added a Boolean element to my REST call to verify if the count reaches threshold and I have moved the actual REST call to Service.ts
With below code I am getting alert message before updating the count on the screen
component.ts
updateCount(inspection, rankName: string, rankPlusOrMinus: string)
{
inspection.rankName = rankName;
inspection.rankPlusOrMinus = rankPlusOrMinus;
this.inspectionService
.update(inspection).then(function(res) {
alert("Reached Threshold: " + res.thresholdReached);
})
.catch(function(rej) {
console.log(rej);
});
}
service.ts
update(inspection: Inspection): Promise<Inspection> {
const url = '/api/inspection/updatecount/';
let rankName = inspection.rankName;
return this.http
.post(url, JSON.stringify(inspection), {headers: this.headers})
.toPromise()
.then(response => {
//this.alertService.success(`Successfully created associate "${associate.name}"!`, true);
let data = response.json();
if (rankName='A') inspection.rankAcount = data.rankAcount;
if (rankName='B') inspection.rankBcount = data.rankBcount;
if (rankName='C') inspection.rankCcount = data.rankCcount;
return response.json() as Inspection;
})
.catch(error => {
});
}
Getting error when I pass $timeout in component.ts
updateCount(inspection, rankName: string, rankPlusOrMinus: string)
{
inspection.rankName = rankName;
inspection.rankPlusOrMinus = rankPlusOrMinus;
this.inspectionService
.update(inspection).then(function(res, $timeout) {
$timeout(function layout() {
alert(res.thresholdReached);
}, 30);
})
.catch(function(rej) {
console.log(rej);
});
}
The way I see it you need to change your code in updateCount cause then method has only one parameter
updateCount(inspection, rankName: string, rankPlusOrMinus: string)
{
inspection.rankName = rankName;
inspection.rankPlusOrMinus = rankPlusOrMinus;
this.inspectionService
.update(inspection).then(function(res) {
setTimeout(_=> alert(res.thresholdReached) , 300); //change code here
})
.catch(rej => console.log(rej));
}
This is how your service.ts should look like:
updateCount(inspection: string, rankName: string, rankPlusOrMinus: string) {
let bodyString = JSON.stringify({ inspection: inspection, rankName: rankName, rankPlusOrMinus: rankPlusOrMinus});
let headers = new HttpHeaders({ 'Content-Type': 'application/JSON' });
return this._http.post<any>(url, bodyString, { headers: headers });
}
getThreshold(rankName: string, rankAcount: number) {
let params = new HttpParams()
.append('rankName', rankName)
.append('rankAcount', rankAcount.toString());
return this._http.get<any>(url, { params: params });
}
This is how your component should look like:
this.inspectionService.updateCount(inspection, rankName, rankPlusOrMinus)
.takeUntil(this.ngUnsubscribe)
.subscribe(
data => { // here you update your counter },
error => { this.errorMessage = <any>error; },
() => {
// Here you check threshold, beacuse this part of code will run last
this.inspectionService.getThreshold(rankName, rankAcount)
.takeUntil(this.ngUnsubscribe)
.subscribe(
data => { // here you do your threshold part
alert("Reached Threshold: " + res.thresholdReached); },
error => { this.errorMessage = <any>error; },
() => {});
});

Correct way to pass async eerror

I have a function which uses Firebase auth to update a user's email:
export const updateEmail = async (email) => {
const user = auth.currentUser;
return user.updateEmail(email);
};
It is used in a function which gets an email from a form (in React) and tries to update the email. If there is an error, we change the state to reflect that.
handleSave = (e) => {
const email = e.target.email.value;
updateEmail(email).catch((err) => {
this.setState({ didError: true, emailError: err.message });
});
};
However, when an error occurs, in the console I get:
My question is: why does this still say 'Uncaught'? Does the .catch() in handleSave not take care of that?
update
Link to relevant Firebase docs
Assuming updateEmail returns a prmise, I guess you can try:
export const updateEmail = (email) => { // no need for async here
const user = auth.currentUser;
return user.updateEmail(email);
};
handleSave = async (e) => {
const email = e.target.email.value;
try{
await updateEmail(email);
}catch(err){
this.setState({ didError: true, emailError: err.message });
}
};
I'm not quite sure since I don't know so much about Firebase, let me suggest something.
export const updateEmail = async (email) => {
const user = auth.currentUser;
const response = await user.updateEmail(email);
if ( response.error ) {
throw new Error( response.error );
}
return "something else";
};

Categories