I'm new at react native and I try to build an mobile app. I'm using Firebase for applications. When I try to login, it stucks on the loading page. And getting this warning:
Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See https://github.com/facebook/react-native/issues/12981 for more info.
(Saw setTimeout with duration 3600000ms)
What should I do?
Also authentication code is here:
import { AsyncStorage } from 'react-native';
export const SIGNUP = 'SIGNUP';
export const LOGIN = 'LOGIN';
export const AUTHENTICATE = 'AUTHENTICATE';
export const LOGOUT = 'LOGOUT';
let timer;
export const authenticate = (userId, token, expiryTime) => {
return dispatch => {
dispatch(setLogoutTimer(expiryTime));
dispatch({ type: AUTHENTICATE, userId: userId, token: token });
};
};
export const signup = (email, password) => {
return async dispatch => {
const response = await fetch(
'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=...',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password,
returnSecureToken: true
})
}
);
if (!response.ok) {
const errorResData = await response.json();
const errorId = errorResData.error.message;
let message = 'Bir terslik var!';
if (errorId === 'EMAIL_EXISTS') {
message = 'Bu e-posta zaten kayıtlı!';
}
throw new Error(message);
}
const resData = await response.json();
console.log(resData);
dispatch(
authenticate(
resData.localId,
resData.idToken,
parseInt(resData.expiresIn) * 1000
)
);
const expirationDate = new Date(
new Date().getTime() + parseInt(resData.expiresIn) * 1000
);
saveDataToStorage(resData.idToken, resData.localId, expirationDate);
};
};
export const login = (email, password) => {
return async dispatch => {
const response = await fetch(
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=...',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password,
returnSecureToken: true
})
}
);
if (!response.ok) {
const errorResData = await response.json();
const errorId = errorResData.error.message;
let message = 'Bir terslik var!';
if (errorId === 'EMAIL_NOT_FOUND') {
message = 'Böyle bir e-posta yok!';
} else if (errorId === 'INVALID_PASSWORD') {
message = 'Bu şifre geçersiz!';
}
throw new Error(message);
}
const resData = await response.json();
console.log(resData);
dispatch(
authenticate(
resData.localId,
resData.idToken,
parseInt(resData.expiresIn) * 1000
)
);
const expirationDate = new Date(
new Date().getTime() + parseInt(resData.expiresIn) * 1000
);
saveDataToStorage(resData.idToken, resData.localId, expirationDate);
};
};
export const logout = () => {
clearLogoutTimer();
AsyncStorage.removeItem('userData');
return { type: LOGOUT };
};
const clearLogoutTimer = () => {
if (timer) {
clearTimeout(timer);
}
};
const setLogoutTimer = expirationTime => {
return dispatch => {
timer = setTimeout(() => {
dispatch(logout());
}, expirationTime);
};
};
const saveDataToStorage = (token, userId, expirationDate) => {
AsyncStorage.setItem(
'userData',
JSON.stringify({
token: token,
userId: userId,
expiryDate: expirationDate.toISOString()
})
);
};
Related
I am new to RTK query and need only one WebSocket connection for my entire application as you can see below I implemented it like an example in GitHub.
I need to somehow send my payload to this WebSocket by subscribing to it.
and then whenever the message comes in I update the other injected Endpoints' cache.
import { ApiSlice } from 'api';
import { instrumentsAdapter } from './marketSlice';
const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
// Connection opened
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
console.log('err', err);
reject(err);
}
});
export const socketApi = ApiSlice.injectEndpoints({
endpoints: (builder) => ({
socketChannel: builder.mutation({
async queryFn(arg) {
await socketConnected;
const { type, topic } = arg;
const sendPayload = { type, path: topic };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
},
async onCacheEntryAdded(arg, { cacheDataLoaded, cacheEntryRemoved }) {
console.log('arg', arg);
await cacheDataLoaded;
// Listen for messages
socket.onmessage = (res) => {
const message = JSON.parse(res.data);
try {
// ApiSlice.util.updateQueryData('getInstrumentByRefId', arg, (draft) => {
// console.log('arg', arg);
// draft = { ...message.value, baseVolume: 3 };
// });
} catch (err) {
console.log('err', err);
}
};
await cacheEntryRemoved;
socket.close();
}
})
})
});
export const { useSocketChannelMutation } = socketApi;
after so much reading docs and researching I finally find this solution working but I do not know if this is a best practice or not.
Here is my not-empty ApiSlice.
/* eslint-disable import/prefer-default-export */
// Or from '#reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi } from '#reduxjs/toolkit/query/react';
import axiosBaseQuery from './axiosBaseQuery';
export const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
reject(err);
}
});
// initialize an empty api service that we'll inject endpoints into later as needed
export const ApiSlice = createApi({
reducerPath: 'api',
baseQuery: axiosBaseQuery(),
endpoints: (builder) => ({
subscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'SUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
unsubscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'UNSUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
channel: builder.mutation({
async queryFn(onMessage) {
await socketConnected;
socket.addEventListener('message', onMessage);
return { data: { messages: [] } };
}
})
})
});
export const { useUnsubscribeSocketMutation, useSubscribeSocketMutation, useChannelMutation } =
ApiSlice;
and this is my enhanced Api slice
import { createEntityAdapter } from '#reduxjs/toolkit';
import { ApiSlice } from 'api';
export const instrumentsAdapter = createEntityAdapter({
selectId: (item) => item?.state?.symbol
});
export const marketApi = ApiSlice.injectEndpoints({
overrideExisting: false,
endpoints: (builder) => ({
getMarketMap: builder.query({
query: (type) => ({
url: `/market/map?type=${type}`,
method: 'get'
})
}),
getInstruments: builder.query({
query: (type) => ({
url: `/market/instruments?type=${type}`,
method: 'get'
})
}),
getInstrumentByRefId: builder.query({
query: (refId) => ({
url: `/market/instruments/${refId}/summary`,
method: 'get'
}),
transformResponse: (res) => {
return instrumentsAdapter.addOne(instrumentsAdapter.getInitialState(), res);
},
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
getCandles: builder.query({
query: ({ refId, bucket, end, limit = 1 }) => ({
url: `/market/instruments/${refId}/candles?bucket=${bucket}&end=${end}&limit=${limit}`,
method: 'get'
})
})
})
});
export const {
useGetMarketMapQuery,
useGetInstrumentByRefIdQuery,
useGetInstrumentsQuery,
useGetCandlesQuery
} = marketApi;
and I try to dispatch my socket endpoints from ApiSlice inside of onCacheEntryAdded.
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
```
I am playing around with Sveltekit and I am struggling a bit..
So my problem is, when I add something to the DB it works, but the new Item does not show in the list until i Refresh the page. My Code looks like:
index.js
import { connectToDatabase } from '$lib/db';
export const post = async ({ request}) => {
const body = await request.json()
console.log(body)
const dbConnection = await connectToDatabase();
const db = dbConnection.db;
const einkaufszettel = db.collection('Einkaufszettel')
await einkaufszettel.insertOne({
name: body.newArticle
});
const einkaufsliste = await einkaufszettel.find().toArray();
return {
status: 200,
body: {
einkaufsliste
}
};
}
export const get = async () => {
const dbConnection = await connectToDatabase();
const db = dbConnection.db;
const einkaufszettel = db.collection('Einkaufszettel')
const einkaufsliste = await einkaufszettel.find().toArray();
console.log(einkaufsliste)
return {
status: 200,
body: {
einkaufsliste,
}
};
}
and the Script of index.svelte
<script>
import Title from '$lib/title.svelte'
export let einkaufsliste = []
let newArticle = ''
const addArticle = async () => {
const res = await fetch('/', {
method: 'POST',
body: JSON.stringify({
newArticle
}),
headers: {
'Content-Type': 'application/json'
}
})
fetchArticles()
}
async function fetchArticles() {
const res = await fetch('/')
console.log(res.body)
}
</script>
In the Network Preview Tab the new Item is already added to the List.
As you can read here, you need to reassign the einkaufsliste after fetching the list of elements from the API.
You can do this in your fetchArticles method, like this:
async function fetchArticles() {
einkaufsliste = await fetch('/')
}
In my project, I am using WordPress woo-commerce as backend and next js as frontend. I am trying to implement stripe payment. I can send line items in stripe sessionData and they show perfectly on the stripe page, how ever when I am trying to send the tax amounts with line items I am getting errors.
import { createCheckoutSession } from "next-stripe/client"; // #see https://github.com/ynnoj/next-stripe
import { loadStripe } from "#stripe/stripe-js";
.....
.....
const createCheckoutSessionAndRedirect = async (orderData) => {
const sessionData = {
success_url:
window.location.origin +
`/thank-you?session_id={CHECKOUT_SESSION_ID}&order_id=${orderData.orderId}`,
cancel_url: window.location.href,
customer_email: orderData.customer_email,
line_items: getStripeLineItems(orderData.products),
metadata: getMetaData(
orderData.billing,
orderData.shipping,
orderData.orderId
),
payment_method_types: ["card"],
mode: "payment",
total_details:{
amount_discount: 0,
amount_shipping: Math.round(10 * 100),
amount_tax: Math.round(10 * 100),
},
};
console.log("Session from another1:", sessionData);
const session = await createCheckoutSession(sessionData);
console.log("Session from another2:", sessionData);
console.log("from another2:", orderData);
try {
console.log("session data", session);
const stripe = await loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
);
if (stripe) {
stripe.redirectToCheckout({ sessionId: session.id });
}
} catch (error) {
console.log(error);
}
};
After many attempts, I could solve the problem.
This is the controller.
const stripe = require("stripe")(process.env.STRIPE_PRIVATE_KEY);
const stripePaymentService = require('../service/stripePaymentService');
module.exports.checkoutSessionCreate = async (req, res) => {
const responseFromService = await stripePaymentService.checkoutSessionCreate(req.body);
console.log("this is session data", responseFromService);
try{
const session = await stripe.checkout.sessions.create(responseFromService);
res.json({ url: session.url });
console.log("success session:", session.url);
}
catch(e){
res.status(500).json({ error: e.message })
console.log("success session:", e.message);
}
}
This is the service...
const stripe = require("stripe")(process.env.STRIPE_PRIVATE_KEY);
const lodash = require('lodash');
module.exports.checkoutSessionCreate = async (serviceData) => {
// console.log(serviceData.products);
const sessionData = {
success_url:`${process.env.CLIENT_URL}/thank-you?session_id={CHECKOUT_SESSION_ID}&order_id=${serviceData.orderId}`,
cancel_url: `${process.env.CLIENT_URL}`,
customer_email: serviceData.customer_email,
line_items: await getStripeLineItems(serviceData.products),
metadata: getMetaData(
serviceData.billing,
serviceData.shipping,
serviceData.orderId
),
payment_method_types: ["card"],
mode: "payment",
};
return sessionData;
}
const getMetaData = (billing, shipping, orderId) => {
return {
billing: JSON.stringify(billing),
shipping: JSON.stringify(shipping),
orderId,
};
};
let getStripeLineItems =async (products) => {
if (lodash.isEmpty(products) && !isArray(products)) {
return [];
}
const productData = await Promise.all(
products.map(async (product) => {
const taxArr = await getTaxID(product.tax_data);
return {
quantity: product?.quantity ?? 0,
name: product?.name ?? "",
images: [product?.images ?? ""],
amount: Math.round(product?.amount * 100),
currency: product?.currency ?? "",
tax_rates: taxArr,
};
})
);
return productData;
};
let getTaxID = async (taxData) => {
let idArr = await Promise.all(
taxData.map(async (item)=>{
const taxRate = await stripe.taxRates.create({
display_name: item.display_name,
inclusive: item.inclusive,
percentage: item.percentage,
});
return taxRate?.id;
})
);
return idArr;
}
I hope this will help somebody....
You cannot set total_details directly as you are trying to do. That is a calculated property of the object (ref), not part of the create endpoint (ref).
To add tax to a Checkout session, you should either use automatic taxes with Stripe Tax, or provide explicit tax rates.
Programming is weird, if you think not then check this case 🤣, I'm using createSlices as Redux and I have two slices with their own states.
First one is orderSlice:
export const orderSlice = createSlice({
name: 'order',
initialState: {
order: null,
message: null,
isLoading: true,
}
})
While the second slice is ordersSlice:
export const orderSlice = createSlice({
name: 'orders',
initialState: {
orders: null,
message: null,
isLoading: true,
}
})
And I have this method to fetch the order and the fulfilled phase where the state is set from:
Fetching the order:
export const fetchOrder = createAsyncThunk('', async ({ token, id }) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/view/${id}`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
Filling the order state:
extraReducers: {
[fetchOrder.fulfilled]: (state, action) => {
state.order = action.payload.data;
state.message = 'Succesfully fetched the Order.';
state.isLoading = false;
}
}
While here is method for fetching the orders:
export const fetchAllOrders = createAsyncThunk('', async (token) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/all`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
And here updating the orders state:
extraReducers: {
[fetchAllOrders.fulfilled]: (state, action) => {
state.orders = action.payload.data;
state.message = 'Succesfully fetched all Orders.';
state.isLoading = false;
}
}
So the case is that I'm calling the fetchAllOrders in the Order page with UseEffect, here is how:
import { fetchAllOrders } from '../redux/ordersSlice';
useEffect(() => dispatch(fetchAllOrders(user.token)), [user]);
So this is how i run the method to fetch orders with dispatch and it works. But the problem is that when I run this function beside the orders state that is filled with the same data, also the order state is filled with the same data and this is impossible as I've cheked all the cases where I could misstyped a user,users typo but there is none I found, and I don't know.
And here is the store:
import orderSlice from './redux/orderSlice';
import ordersSlice from './redux/ordersSlice';
const store = configureStore({
reducer: {
order: orderSlice,
orders: ordersSlice
},
});
You have to give your thunks an unique name: If you name both '' they will be handled interchangably.
Also, you should be using the builder notation for extraReducers. We will deprecate the object notation you are using soon.
I'm trying to test a 'redux observable epic' but the test fail because not all actions are in store.getActions() the strange is the store.dispatch function runs.
Epic and actions
export const VERIFY_SESION = 'auth/VERIFY_SESION';
export const SET_POLICIES_ACCEPTED = 'auth/SET_POLICIES_ACCEPTED';
export const AUTHENTICATE = 'auth/AUTHENTICATE';
export function setPoliciesAccepted(wereAccepted: boolean) {
return {
wereAccepted,
type: SET_POLICIES_ACCEPTED,
};
}
export function verifySesion() {
return {
type: VERIFY_SESION,
};
}
export function authenticate(token) {
return {
token,
type: AUTHENTICATE,
};
}
export function verifySesionEpic(action$, store) {
return action$
.ofType(VERIFY_SESION)
.switchMap(async () => {
try {
store.dispatch(setBlockLoading(true));
const token = await AsyncStorage.getItem('token');
if (token !== null) {
store.dispatch(setBlockLoading(false));
return authenticate(token);
}
const policiesWereAccepted = await AsyncStorage.getItem('policiesWereAccepted');
store.dispatch(setBlockLoading(false));
return setPoliciesAccepted(policiesWereAccepted);
} catch (error) {
return setMessage(error.message);
}
});
}
test
describe('actions/auth', () => {
let store;
const asyncStorageGetStub = stub(AsyncStorage, 'getItem');
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
asyncStorageGetStub.restore();
});
it('Should call authenticate if token', () => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
});
});
Test result
1) "actions/auth Should call epic for verifySesion:
Error: Expected [ { type: 'auth/VERIFY_SESION' } ] to include { token: 'mitoken', type: 'auth/AUTHENTICATE' }"
Note
im sure that the conditional token !== null pass
I was to add a timeout before getAction because the 'AUTHENTICATE' actions is added after.
it('Should call authenticate if token', (done) => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
setTimeout(() => {
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
done();
}, 1000);
});