About getting data from RTK Query to Firebase Realtime Database - javascript

I am currently trying to get data from a Firebase Realtime Database using RTK Query. However, the code here is giving me an error because the value returned in return is not correct. If anyone has knowledge of this, I would appreciate it if you could correct the code to the correct one.
import { createApi, fakeBaseQuery } from "#reduxjs/toolkit/query/react";
import { onValue, ref } from "firebase/database";
import { db } from "libs/firebase";
export const userApi = createApi({
baseQuery: fakeBaseQuery(),
endpoints: builder => ({
getUser: builder.query({
queryFn(uid) {
try {
onValue(ref(db, `users/user${uid}`), snapshot => {
return { data: snapshot.val() };
});
} catch (e) {
return { error: e };
}
},
}),
}),
});
export const { useGetUserQuery } = userApi;
import { configureStore } from "#reduxjs/toolkit";
import { userApi } from "./apiSlice";
export const store = configureStore({
reducer: {
[userApi.reducerPath]: userApi.reducer,
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(userApi.middleware),
});
const { data: user, error, isLoading, isSuccess } = useGetUserQuery(1);
console.log(user);
error message

Try something along the lines of
export const userApi = createApi({
baseQuery: fakeBaseQuery(),
endpoints: (builder) => ({
getUser: builder.query({
async queryFn(uid) {
try {
const data = await new Promise((resolve, reject) =>
onValue(
ref(db, `users/user${uid}`),
(snapshot) => resolve(snapshot.toJSON()),
reject
)
);
return { data };
} catch (e) {
return { error: e };
}
},
}),
}),
});

Related

Testing a react-query hook that uploads a binary file

There is a unit test that's failing on the following react-query hook.
The hook works fine otherwise, it uploads a binary file to an endpoint.
Here is the hook:
import { useMutation, useQueryClient } from 'react-query';
import { IUploadFieldRequest } from '../constants/types';
import { licenseServiceAPI } from '../services';
export const URI = () => '/licenses/upload';
export const useUploadLicense = () => {
const queryClient = useQueryClient();
const [apiService] = licenseServiceAPI<IUploadFieldRequest>({
headers: {
'Content-Type': 'multipart/form-data'
}
});
const {
data: response,
mutateAsync,
isLoading,
isSuccess
} = useMutation(
(formData: IUploadFieldRequest) => {
const form = new FormData();
Array.from(formData.files)?.forEach((file) => {
form.append('license-file', file);
});
return apiService.post(URI(), form);
},
{
onError: () => {
return queryClient.invalidateQueries('licenseUpload');
}
}
);
return {
data: response?.data,
uploadFiles: mutateAsync,
isLoading,
isSuccess
};
};
export default useUploadLicense;
And this is the test for it:
import { act, renderHook } from '#testing-library/react-hooks';
import { queryClientInstance } from '#oam/shared/test-utils';
import useUploadLicense from './use-upload-license';
const file = new File(['file'], 'license-file.json', { type: 'multipart/form-data' });
it('upload files', async () => {
const { result, waitFor } = renderHook(() => useUploadLicense(), {
wrapper: queryClientInstance
});
act(() => {
result.current.uploadFiles({ files: [file] });
});
await waitFor(() => expect(result.current.isLoading).toBeTruthy());
await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
});
The error says it has Timed out in waitFor after 1000ms. and that's a Network error
Any ideas how to fix this?
Also, it could be useful to know the interface:
type IUploadFieldRequest = {
files: File[];
};
And the service:
import { AxiosRequestConfig } from 'axios';
import { requestConfig } from '#oam/shared/services';
import init, { Response } from '#oam/shared/utils/request-wrapper';
export const BASE_PATH =
process.env.NODE_ENV === 'development'
? '/myurl/v1'
: 'http://localhost:7999/v1';
export const licenseServiceAPI = <TRequest, TResponse = TRequest>(
config?: AxiosRequestConfig
): [Response<TRequest, TResponse>, AxiosRequestConfig] => {
const newConfig = requestConfig({
baseURL: BASE_PATH,
headers: {
Accept: 'application/json',
...config?.headers
},
responseType: config?.responseType ? config?.responseType : undefined
});
return [init(newConfig), newConfig];
};
export default licenseServiceAPI;

getting error while firebase version 9 FCM implementation in Next 12

I wanted to add push notification on my next js(version 12) app. so implemented firebase Cloud messaging on that. implementation looks like this :
import { initializeApp, getApp, getApps } from "firebase/app"
import { getMessaging, getToken } from "firebase/messaging"
import { firebaseConfig } from "./config"
const app = !getApps.length ? initializeApp(firebaseConfig) : getApp()
and added this cloudMessaging function for getting FCM token. and onMessageListener function for displaying foreground messages
export const cloudMessaging = async () => {
const token = await isTokenAvailable()
if (token !== null) {
return Promise.resolve({
status: true,
token: token,
})
}
try {
const permission = await Notification.requestPermission()
const messaging = getMessaging(app)
console.log(messaging)
console.log(permission)
if (permission === "granted") {
const FCM_TOKEN = await getToken(messaging, {
vapidKey: process.env.NEXT_PUBLIC_FCM_VAPID_KEY,
})
if (FCM_TOKEN) {
localStorage.setItem("fcm_token_prac", FCM_TOKEN)
return Promise.resolve({
status: true,
token: FCM_TOKEN,
})
}
}
} catch (err) {
console.log(err, "cloudmessaging error")
return Promise.resolve({
status: false,
})
}
}
export const onMessageListener = () => {
const messaging = getMessaging(app)
console.log(messaging)
return new Promise((res) => {
messaging.onMessage((payload) => {
res(payload)
})
})
}
And invoking these function from my Layout component
useEffect(() => {
firebaseInit()
async function firebaseInit() {
try {
await cloudMessaging()
} catch (err) {
console.log(err)
}
}
}, [])
useEffect(() => {
onMessageListener()
.then((payload) => {
console.log(payload, "onMessageListener")
})
.catch((err) => console.log(err, "onMessageListener useEffect"))
}, [])
But getting this error in my console :
TypeError: messaging.onMessage is not a function
at eval (firbase.js?100f:58:15)
at new Promise (<anonymous>)
at onMessageListener (firbase.js?100f:57:10)
at eval (Layout.js?4f8d:33:22)
at commitHookEffectListMount (react-dom.development.js?ac89:23049:1)
I'm unable to find out where I made the mistake. Can anyone guide me to implement this?
I think you can try adding onMessage in the import part.
import { getMessaging, getToken, onMessage } from "firebase/messaging"
Remove messaging on line
messaging.onMessage
becomes
onMessage((payload) => {
res(payload)
})
This is for SDK version 9

When routing mswjs/data populates the database with new items and removes the previous one, making it inaccessible

I use next-redux-wrapper, MSW, #mswjs/data and redux-toolkit for storing my data in a store as well as mocking API calls and fetching from a mock Database.
I have the following scenario happening to me.
I am on page /content/editor and in the console and terminal, I can see the data was fetched from the mock database and hydrated from getStaticProps of Editor.js. So now IDs 1 to 6 are inside the store accessible.
Now I click on the PLUS icon to create a new project. I fill out the dialog and press "SAVE". a POST request starts, it's pending and then it gets fulfilled. The new project is now in the mock DB as well as in the store, I can see IDs 1 to 7 now.
Since I clicked "SAVE" and the POST request was successful, I am being routed to /content/editor/7 to view the newly created project.
Now I am on Page [id].js, which also fetched data from the mock DB and then it gets stored and hydrated into the redux store. The idea is, it takes the previous store's state and spreads it into the store, with the new data (if there are any).
Now the ID 7 no longer exists. And IDs 1 to 6 also don't exist anymore, instead, I can see in the console and terminal that IDs 8 to 13 were created, and the previous ones are no more.
Obviously, this is not great. When I create a new project and then switch the route, I should be able to access the newly created project as well as the previously created ones. But instead, they all get overwritten.
It either has something to do with the next-redux-wrapper or MSW, but I am not sure how to make it work. I need help with it. I will post some code now:
Code
getStaticProps
// path example: /content/editor
// Editor.js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale }) => {
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
},
};
}
);
// path example: /content/editor/2
// [id].js
export const getStaticProps = wrapper.getStaticProps(
(store) =>
async ({ locale, params }) => {
const { id } = params;
const [translation] = await Promise.all([
serverSideTranslations(locale, ['editor', 'common', 'thesis']),
store.dispatch(fetchProjects()),
// store.dispatch(fetchProjectById(id)), // issue: fetching by ID returns null
store.dispatch(fetchBuildingBlocks()),
]);
return {
props: {
...translation,
id,
},
};
}
);
Mock Database
Factory
I am going to shorten the code to the relevant bits. I will remove properties for a project, as well es helper functions to generate data.
const asscendingId = (() => {
let id = 1;
return () => id++;
})();
const isDevelopment =
process.env.NODE_ENV === 'development' || process.env.STORYBOOK || false;
export const projectFactory = () => {
return {
id: primaryKey(isDevelopment ? asscendingId : nanoid),
name: String,
// ... other properties
}
};
export const createProject = (data) => {
return {
name: data.name,
createdAt: getUnixTime(new Date()),
...data,
};
};
/**
* Create initial set of tasks
*/
export function generateMockProjects(amount) {
const projects = [];
for (let i = amount; i >= 0; i--) {
const project = createProject({
name: faker.lorem.sentence(faker.datatype.number({ min: 1, max: 5 })),
dueDate: date(),
fontFamily: getRandomFontFamily(),
pageMargins: getRandomPageMargins(),
textAlign: getRandomTextAlign(),
pageNumberPosition: getRandomPageNumberPosition(),
...createWordsCounter(),
});
projects.push(project);
}
return projects;
}
API Handler
I will shorten this one to GET and POST requests only.
import { db } from '../../db';
export const projectsHandlers = (delay = 0) => {
return [
rest.get('https://my.backend/mock/projects', getAllProjects(delay)),
rest.get('https://my.backend/mock/projects/:id', getProjectById(delay)),
rest.get('https://my.backend/mock/projectsNames', getProjectsNames(delay)),
rest.get(
'https://my.backend/mock/projects/name/:id',
getProjectsNamesById(delay)
),
rest.post('https://my.backend/mock/projects', postProject(delay)),
rest.patch(
'https://my.backend/mock/projects/:id',
updateProjectById(delay)
),
];
};
function getAllProjects(delay) {
return (request, response, context) => {
const projects = db.project.getAll();
return response(context.delay(delay), context.json(projects));
};
}
function postProject(delay) {
return (request, response, context) => {
const { body } = request;
if (body.content === 'error') {
return response(
context.delay(delay),
context.status(500),
context.json('Server error saving this project')
);
}
const now = getUnixTime(new Date());
const project = db.project.create({
...body,
createdAt: now,
maxWords: 10_000,
minWords: 7000,
targetWords: 8500,
potentialWords: 1500,
currentWords: 0,
});
return response(context.delay(delay), context.json(project));
};
}
// all handlers
import { buildingBlocksHandlers } from './api/buildingblocks';
import { checklistHandlers } from './api/checklist';
import { paragraphsHandlers } from './api/paragraphs';
import { projectsHandlers } from './api/projects';
import { tasksHandlers } from './api/tasks';
const ARTIFICIAL_DELAY_MS = 2000;
export const handlers = [
...tasksHandlers(ARTIFICIAL_DELAY_MS),
...checklistHandlers(ARTIFICIAL_DELAY_MS),
...projectsHandlers(ARTIFICIAL_DELAY_MS),
...buildingBlocksHandlers(ARTIFICIAL_DELAY_MS),
...paragraphsHandlers(ARTIFICIAL_DELAY_MS),
];
// database
import { factory } from '#mswjs/data';
import {
buildingBlockFactory,
generateMockBuildingBlocks,
} from './factory/buildingblocks.factory';
import {
checklistFactory,
generateMockChecklist,
} from './factory/checklist.factory';
import { paragraphFactory } from './factory/paragraph.factory';
import {
projectFactory,
generateMockProjects,
} from './factory/project.factory';
import { taskFactory, generateMockTasks } from './factory/task.factory';
export const db = factory({
task: taskFactory(),
checklist: checklistFactory(),
project: projectFactory(),
buildingBlock: buildingBlockFactory(),
paragraph: paragraphFactory(),
});
generateMockProjects(5).map((project) => db.project.create(project));
const projectIds = db.project.getAll().map((project) => project.id);
generateMockTasks(20, projectIds).map((task) => db.task.create(task));
generateMockBuildingBlocks(10, projectIds).map((block) =>
db.buildingBlock.create(block)
);
const taskIds = db.task.getAll().map((task) => task.id);
generateMockChecklist(20, taskIds).map((item) => db.checklist.create(item));
Project Slice
I will shorten this one as well to the relevant snippets.
// projects.slice.js
import {
createAsyncThunk,
createEntityAdapter,
createSelector,
createSlice,
current,
} from '#reduxjs/toolkit';
import { client } from 'mocks/client';
import { HYDRATE } from 'next-redux-wrapper';
const projectsAdapter = createEntityAdapter();
const initialState = projectsAdapter.getInitialState({
status: 'idle',
filter: { type: null, value: null },
statuses: {},
});
export const fetchProjects = createAsyncThunk(
'projects/fetchProjects',
async () => {
const response = await client.get('https://my.backend/mock/projects');
return response.data;
}
);
export const saveNewProject = createAsyncThunk(
'projects/saveNewProject',
async (data) => {
const response = await client.post('https://my.backend/mock/projects', {
...data,
});
return response.data;
}
);
export const projectSlice = createSlice({
name: 'projects',
initialState,
reducers: {
// irrelevant reducers....
},
extraReducers: (builder) => {
builder
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
.addCase(fetchProjects.pending, (state, action) => {
state.status = 'loading';
})
.addCase(fetchProjects.fulfilled, (state, action) => {
projectsAdapter.addMany(state, action.payload);
state.status = 'idle';
action.payload.forEach((item) => {
state.statuses[item.id] = 'idle';
});
})
.addCase(saveNewProject.pending, (state, action) => {
console.log('SAVE NEW PROJECT PENDING', action);
})
.addCase(saveNewProject.fulfilled, (state, action) => {
projectsAdapter.addOne(state, action.payload);
console.group('SAVE NEW PROJECT FULFILLED');
console.log(current(state));
console.log(action);
console.groupEnd();
state.statuses[action.payload.id] = 'idle';
})
// other irrelevant reducers...
},
});
This should be all the relevant code. If you have questions, please ask them and I will try to answer them.
I have changed how the state gets hydrated, so I turned this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.log('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
return {
...state,
...action.payload.projects,
statuses,
};
})
Into this code:
.addCase(HYDRATE, (state, action) => {
// eslint-disable-next-line no-console
console.group('HYDRATE', action.payload);
const statuses = Object.fromEntries(
action.payload.projects.ids.map((id) => [id, 'idle'])
);
state.statuses = { ...state.statuses, ...statuses };
projectsAdapter.upsertMany(state, action.payload.projects.entities);
})
I used the adapter to upsert all entries.

React-Redux - Parsing error: Unexpected token, expected "," in action creator

I am working on a React application and I am using Redux to store the state. I have the following code:
menu.types.js:
export const FETCH_CATEGORY_RANKS = "FETCH_CATEGORY_RANKS";
menu.actions.js:
import { apiUrl, apiConfig } from '../../util/api';
import { FETCH_CATEGORY_RANKS } from './menu.types';
export const fetchCategoryRanks = menu => async dispatch => {
console.log("Printing menu (fetch category ranks)");
console.log(menu);
menu.map(async (category) {
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
const response = await fetch(`${apiUrl}/category/${category._id}`, options)
let data = await response.json()
if (response.ok) {
console.log("It got sent")
} else {
alert(data.error)
}
});
dispatch({ type: FETCH_CATEGORY_RANKS, menu });
}
menu.reducer.js:
// import INITIAL_STATE from './menu.data';
import { FETCH_CATEGORY_RANKS } from './menu.types';
const INITIAL_STATE = []
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_CATEGORY_RANKS:
return state;
default:
return state;
}
}
When I run my application, I am getting the following error in the action creator fetchCategoryRanks:
I am not sure what I am missing from this function, as all the curly braces match up with each other. Any insights are appreciated.
Missing => in map
menu.map(async (category) => {
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
As Han stated, you're missing => in your map's arrow function:
menu.map(async (category) => {
const options = {
...apiConfig(),
method: 'PUT',
body: JSON.stringify(category)
}
...
}

Mock inner axios.create()

I'm using jest and axios-mock-adapter to test axios API calls in redux async action creators.
I can't make them work when I'm using a axios instance that was created with axios.create() as such:
import axios from 'axios';
const { REACT_APP_BASE_URL } = process.env;
export const ajax = axios.create({
baseURL: REACT_APP_BASE_URL,
});
which I would consume it in my async action creator like:
import { ajax } from '../../api/Ajax'
export function reportGet(data) {
return async (dispatch, getState) => {
dispatch({ type: REQUEST_TRANSACTION_DATA })
try {
const result = await ajax.post(
END_POINT_MERCHANT_TRANSACTIONS_GET,
data,
)
dispatch({ type: RECEIVE_TRANSACTION_DATA, data: result.data })
return result.data
} catch (e) {
throw new Error(e);
}
}
}
Here is my test file:
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
let mock = new MockAdapter(axios)
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})
And I only get one action Received: [{"type": "REQUEST_TRANSACTION_DATA"}] because there was an error with the ajax.post.
I have tried many ways to mock the axios.create to no avail without really knowing what I'm doing..Any Help is appreciated.
OK I got it. Here is how I fixed it! I ended up doing without any mocking libraries for axios!
Create a mock for axios in src/__mocks__:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios
Then in your test file, the gist would look like:
import mockAxios from 'axios'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
// for some reason i need this to fix reducer keys undefined errors..
jest.mock('../../store/rootStore.ts')
// you need the 'async'!
test('Retrieve transaction data based on a date range', async () => {
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore()
const mockData = {
'data': 123
}
/**
* SETUP
* This is where you override the 'post' method of your mocked axios and return
* mocked data in an appropriate data structure-- {data: YOUR_DATA} -- which
* mirrors the actual API call, in this case, the 'reportGet'
*/
mockAxios.post.mockImplementationOnce(() =>
Promise.resolve({ data: mockData }),
)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
// work
await store.dispatch(reportGet())
// assertions / expects
expect(store.getActions()).toEqual(expectedActions)
expect(mockAxios.post).toHaveBeenCalledTimes(1)
})
If you need to create Jest test which mocks the axios with create in a specific test (and don't need the mock axios for all test cases, as mentioned in other answers) you could also use:
const axios = require("axios");
jest.mock("axios");
beforeAll(() => {
axios.create.mockReturnThis();
});
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
Here is the link to the same example of Axios mocking in Jest without create. The difference is to add axios.create.mockReturnThis()
here is my mock for axios
export default {
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
create: jest.fn(function () {
return {
interceptors:{
request : {
use: jest.fn(() => Promise.resolve({ data: {} })),
}
},
defaults:{
headers:{
common:{
"Content-Type":"",
"Authorization":""
}
}
},
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
}
}),
};
In your mockAdapter, you're mocking the wrong instance. You should have mocked ajax instead. like this, const mock = MockAdapter(ajax)
This is because you are now not mocking the axios instance but rather the ajax because it's the one you're using to send the request, ie, you created an axios instance called ajax when you did export const ajax = axios.create...so since you're doing const result = await ajax.post in your code, its that ajax instance of axios that should be mocked, not axios in that case.
I have another solution.
import {
reportGet,
REQUEST_TRANSACTION_DATA,
RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
// import axios from 'axios'
import { ajax } from '../../api/Ajax' // axios instance
import MockAdapter from 'axios-mock-adapter'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })
test('get report data', async () => {
// let mock = new MockAdapter(axios)
let mock = new MockAdapter(ajax) // this here need to mock axios instance
const mockData = {
totalSalesAmount: 0
}
mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)
const expectedActions = [
{ type: REQUEST_TRANSACTION_DATA },
{ type: RECEIVE_TRANSACTION_DATA, data: mockData },
]
await store.dispatch(reportGet())
expect(store.getActions()).toEqual(expectedActions)
})
another method: add this file to src/__mocks__ folder
import { AxiosStatic } from 'axios';
const axiosMock = jest.createMockFromModule<AxiosStatic>('axios');
axiosMock.create = jest.fn(() => axiosMock);
export default axiosMock;
The following code works!
jest.mock("axios", () => {
return {
create: jest.fn(() => axios),
post: jest.fn(() => Promise.resolve()),
};
});

Categories

Resources