How to update state of messages in react-native-gifted-chat? - reactjs

I relying on API calls for sending messages using react-native-gifted-chat in a react native app, I want the flow to be like when the user clicks on the send button it should have the state as pending:true and when the API call is a success I want it to have it as pending:false,sent:true I can achieve the first part but even when the API call is a success it does not update the state. Below is my implementation of it, I am trying to follow solution posted [here][1] but I think something is wrong with my implementation, Please let me know if any clarification is required.
function SmsConversations({ route }) {
const onSend = useCallback((messages = []) => {
const [messageToSend] = messages;
messageToSend.pending = true;
sendSMS(toNumber, fromNumber, messageToSend.text)
.then((res) => {
messageToSend.pending = false;
messageToSend.sent = true;
})
.catch((err) => (messageToSend.pending = true))
.done();
setMessages((previousMessages) =>
GiftedChat.append(previousMessages, messageToSend)
);
}, []);
return (
<GiftedChat
messages={messages}
onSend={(messages) => onSend(messages)}
user={{
_id: 1,
}}
/>
);
}
Also if the API is errored is there a way to handle it using react-native-gifted-chat, like failed or something?

Related

How to refresh the value stored in context on an action?

So, i am fetching data from api and doing some manipulation on it and providing it to a component from context.
After I update the form i want to fetch new values and do the same thing again but its not being called automatically
const { isLoading: assayMatrixIsLoading, data: matrixData } = useQuery('Assay Matrix Data', fetchAssayMatrixes);
useEffect(() => {
// console.log('context mount');
}, []);
useEffect(() => {
if (!assayMatrixIsLoading) {
const unflattenedData = getDataForAutocomplete(unflattenObject(matrixData));
setDataForAutoComplete(unflattenedData);
}
}, [assayMatrixIsLoading]);
return dataForAutoComplete ? (
<AssayContext.Provider
value={{
dataForAutoComplete,
validation,
...
<AssayContextProvider>
<CreateAssayStepper />
</AssayContextProvider>
so how can i auto call it let say on route change or on submit like that?

Reactjs updated prop is not shown

I tried to create a interactable map following this example here: https://docs.mapbox.com/mapbox-gl-js/example/cluster/
In my componentDidMount (where I create a mapboxgl) I implemented clickable markers, when clicked on the markers a popup appears which displays various informations.
After the click I want to call a second function (fetch) to get more data on that specific marker: this.props.getData(id);
I then want to display these data in the same popup as the other information.
My problem is that this.props.testdata is empty on the first click. If I double-click on the marker, the data appear. So my guess is that my component does not notice the change of the state/prop and therefore does not update?
How do I do that or what am I missing?
Map.js
this.map.on('click', 'unclustered-point', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const id = e.features[0].properties.id;
const infos = e.features[0].properties.infos;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
if (id == null) {
console.log("Missing id, cant get informations")
return;
}
this.props.getData(id);
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(
`
Id: ${id}
<br>
Infos: ${infos}
<br>
<br>
Testdata: ${this.props.testdata}
`
)
.addTo(this.map);
});
this.map.on('mouseenter', 'clusters', () => {
this.map.getCanvas().style.cursor = 'pointer';
});
this.map.on('mouseleave', 'clusters', () => {
this.map.getCanvas().style.cursor = '';
});
});
App.js (getData function):
getData = (id) => {
if (id== null) {
console.log("Missing id")
return;
}
const {mapCenter, startDate, endDate} = this.state;
const neo4j = require('neo4j-driver')
const driver = neo4j.driver('bolt://xxx', neo4j.auth.basic("xx", "xx-xx"))
const session = driver.session()
session
.run('Here goes a neo4j cypher statment',{id: id})
.then((results)=> {
const data= [];
results.records.forEach((record) => data.push([record.get("r"), record.get("n"), record.get("b")]))
this.setState({
data
});
session.close()
driver.close()
}).catch(e => {
console.log(e)
session.close();
});
};
I am not familiar with neo4j, but it is apparent that getData(id) fetches data from a server. This is going to be an asynchronous operation, so you should add a state property to maybe show a spinner while data is being fetched?
Regarding testdata not being available, I do not see the code where it is being set.
Maybe your setState code should be:
this.setState({
testdata: data
});
//If your data prop is testdata.
As per the current setState, data property of your component state would be set with server response.
Updates:
Temporary fix for async server call:
You can change following methods and try if it fixes your issue:
this.map.on('click', 'unclustered-point', async (e) => {
// ...previous code
await this.props.getData(id);
// This forces the following code to execute synchronously. Basically it should wait for your API call to be complete
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(
`
Id: ${id}
<br>
Infos: ${infos}
<br>
<br>
Testdata: ${this.props.testdata}
`
)
.addTo(this.map);
});
this.map.on('mouseenter', 'clusters', () => {
this.map.getCanvas().style.cursor = 'pointer';
});
this.map.on('mouseleave', 'clusters', () => {
this.map.getCanvas().style.cursor = '';
});
});
getData = (id) => {
//... previous code
// we return a promise to use await in the onClick handler
return session
.run('Here goes a neo4j cypher statment',{id: id})
.then((results)=> {
const data= [];
results.records.forEach((record) => data.push([record.get("r"), record.get("n"), record.get("b")]))
this.setState({
data
});
session.close()
driver.close()
}).catch(e => {
console.log(e)
session.close();
});
}
If you are still facing an issue, please create a sample app and share.
I have not yet managed to fix the original problem.
However, I have found another solution:
In my Map.js I'm calling the this.props.testdata in th UI like this:
<div className="sidebar">
info: {JSON.stringify(this.props.testdata)}
</div>

store data in firestore when Browser Tab is closed or the route is changed (react JS)

const handleDraftContracts = async () => {
console.log('/bruhhhhhhandleDraftContract');
const paragraphRef: string | any = document.getElementById('contract');
const contractDetails = {
contractName: 'House Rental',
states: {
amount: amount,
},
content: paragraphRef?.textContent,
};
await makeDraftContract(contractDetails);
};
useEffect(() => {
console.log('///////I am hreeeee');
window.addEventListener('onbeforeunload', (env) => {
handleDraftContracts();
});
return () => {
console.log('///////removing');
window.removeEventListener('onbeforeunload', handleDraftContracts);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
firestore.js
// make Draft Contracts
export async function makeDraftContract(contractDetails: object | any) {
try {
console.log("making a draft contract", contractDetails);
const draftContractRef: any = collection(db,"makeDraftContracts");
let contract = await addDoc(draftContractRef, contractDetails);
console.log("./////////makeDraftContract", contract);
} catch (error) {
console.log('////errror in contract Hanlder', error);
}
}
I want to call my handleDraftContracts method whenever user closes the tab or changes the route. I am using onbeforeunload event. The handleDraftContracts is getting called but the tab unloads before Firestore could update the collection. How can I get around this that as the user closes the tab or move to a new route, my firestore method get executed first then the tab gets unloaded ?
Try with Beacon api
https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API
as 'onbeforeunload' cannot make sure you request to server has been made and requests can slow down the browser
componentWillUnmount is like that one, cannot to make long running script.

How to reset recaptcha when using react-redux-firebase

I am working with React-Redux-Firebase. I implemented signing in with phone number. Now I am trying to implement error handling. When number is invalid I display window alert with error message. The only thing left to do is to reset recaptcha. Without it, I am getting error:
reCAPTCHA has already been rendered in this element
I was trying to do according to Firebase documentation
grecaptcha.reset(window.recaptchaWidgetId);
// Or, if you haven't stored the widget ID:
window.recaptchaVerifier.render().then(function(widgetId) {
grecaptcha.reset(widgetId);
}
but it does not work in my code. I dont have grecaptcha implemented. I tried to add it with react-grecaptcha, but it did not work.
Could someone give me a hint how to reset recaptcha after each error, please?
state = {
phone: "",
confirmationResult: {},
};
handleClick = () => {
const recaptchaVerifier = new firebase.auth.RecaptchaVerifier(
"sign-in-button",
{
size: "invisible",
}
);
firebase
.signInWithPhoneNumber(`+${this.state.phone}`, recaptchaVerifier)
.then((confirmationResult) => {
this.setState({ confirmationResult });
})
.catch((error) => {
// Error; SMS not sent
// Handle Errors Here
window.alert(`${error.code}, ${error.message}`);
recaptchaVerifier.reset(); // How can I do that?
});
};
I've been struggling with this problem for several days, maybe my answer will help someone.
export const requestRecaptchVerifier = () => {
window.recaptchaVerifier = new RecaptchaVerifier(
"recapcha-container",
{
size: "invisible",
},
auth
);
};
I then call signInWithPhone from another function and handle the error like this:
await signInWithPhone(formik.values.phone)
.then(() => {
// ... my code
})
.catch(() => {
window.recaptchaVerifier.recaptcha.reset();
window.recaptchaVerifier.clear();
});
All the difference in
window.recaptchaVerifier.recaptcha.reset()
And
window.recaptchaVerifier.clear()
I'm no expert but from the documentation and by talking with you in the comment section I think you need to pass a callback. Like this:
const recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', {
'size': 'invisible',
'callback': function(response) {
// reCAPTCHA solved, allow signInWithPhoneNumber.
firebase
.signInWithPhoneNumber(`+${this.state.phone}`, recaptchaVerifier)
.then((confirmationResult) => {
this.setState({ confirmationResult });
})
.catch((error) => {
// Error; SMS not sent
// Handle Errors Here
window.alert(`${error.code}, ${error.message}`);
recaptchaVerifier.reset();
});
}
});
Reference: https://firebase.google.com/docs/auth/web/phone-auth#use-invisible-recaptcha
Hope this helps!

show confirmation dialog using redux-observable

I'm trying to implement a delete epic with a confirmation dialog.
I came up with this approach. It has the advantage of being easy to test.
My question is, is this a good approach, should I worry about adding takeUntil(action$.ofType(MODAL_NO_CLICKED))?
Please let me know if you can think of a better way to implement this.
const deleteNotification$ = (id, { ajax }) => ajax({ url: `api/delete/{id}` });
// showYesNo is an action to eventually show a dialog using this approach https://stackoverflow.com/a/35641680/235659
const showYesNo = payload => ({
type: SHOW_MODAL,
modalType: MODAL_TYPE_YES_NO,
modalProps: { ...payload },
});
const deleteNotificationEpic = (action$, store, dependencies) => {
let uid = dependencies.uid; // dependencies.uid is added here to allow passing the uid during unit test.
return merge(
// Show confirmation dialog.
action$.pipe(
ofType(NOTIFICATION_DELETE_REQUEST),
map(action => {
uid = shortid.generate();
return showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
notificationId: action.notificationId,
uid,
},
})
}),
),
// Deletes the notification if the user clicks on Yes
action$.pipe(
ofType(MODAL_YES_CLICKED),
filter(({ payload }) => payload.uid === uid),
mergeMap(({ payload }) =>
deleteNotification$(payload.notificationId, dependencies).pipe(
mergeMap(() => of(deleteNotificationSuccess())),
catchError(error => of(deleteNotificationSuccess(error))),
),
),
),
);
};
I know I can show the confirmation dialog on the React level and only dispatch the delete action if the user clicks on Yes, but my question is a more general case where I might have some logic (calling the back-end) before deciding to show the confirmation dialog or not.
Your solution is generally good. There is a potential for weird bugs since MODAL_YES_CLICKED is always listened for even if the notification isn't displayed, though whether this matters is debatable.
When I need similar patterns I personally set up the listener only as-needed and make sure to have some way to cancel (like MODAL_NO_CLICKED) so I don't leak memory. Putting it more sequentially like this helps me understand the expected flow.
return action$.pipe(
ofType(NOTIFICATION_DELETE_REQUEST),
switchMap(action => {
uid = shortid.generate();
return action$.pipe(
ofType(MODAL_YES_CLICKED),
filter(({ payload }) => payload.uid === uid),
take(1),
mergeMap(({ payload }) =>
deleteNotification$(payload.notificationId, dependencies).pipe(
map(() => deleteNotificationSuccess()),
catchError(error => of(deleteNotificationSuccess(error))),
),
),
takeUntil(action$.pipe(ofType(MODAL_NO_CLICKED))),
startWith(
showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
notificationId: action.notificationId,
uid,
},
})
)
)
}),
)
One interesting thing about my approach vs. yours is that mine is a bit more verbose because I need to have takeUntil as well as take(1) (so we don't leak memory).
Unit test:
it('should delete the notification when MODAL_YES_CLICKED is dispatched', () => {
const uid = 1234;
shortid.generate.mockImplementation(() => uid);
const store = null;
const dependencies = {
ajax: () => of({}),
uid,
};
const inputValues = {
a: action.deleteNotificationRequest(12345, uid),
b: buttonYesClicked({ id: 12345, uid }),
};
const expectedValues = {
a: showYesNo({
message: 'NOTIFICATION_DELETE_CONFIRMATION',
payload: {
id: 12345,
uid,
},
}),
b: showToastSuccessDeleted(),
c: action.loadNotificationsRequest(false),
};
const inputMarble = ' a---b';
const expectedMarble = '---a---(bc)';
const ts = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
const action$ = new ActionsObservable(ts.createHotObservable(inputMarble, inputValues));
const outputAction = epic.deleteNotificationEpic(action$, store, dependencies);
ts.expectObservable(outputAction).toBe(expectedMarble, expectedValues);
ts.flush();
});
As comments are restricted in length, I'm posting an answer even though it's not yet one.
I don't think I can given you guidance as the example code is missing implementations so it's not clear what exactly happens. In particular, what is showYesNo and deleteNotification$?
Btw, the unique ID you generate is only done once, when the epic starts up. That seems like it would be a bug since unique IDs are not generally reusable?
const deleteNotificationEpic = (action$, store, dependencies) => {
const uid = shortid.generate();

Resources