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

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)
}
...
}

Related

About getting data from RTK Query to Firebase Realtime Database

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 };
}
},
}),
}),
});

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;

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.

How to pre-fetch data using prefetchQuery with React-Query

I am trying to pre-fetch data using react-query prefetchQuery. When I am inspecting browser DevTools network tab I can see that data that was requested for prefetchQuery is coming from the back-end but for some reason when I look into react-query DevTools it does generate the key in the cache but for some reason the Data is not there. Let me know what I am doing wrong.
import { useState, useEffect } from 'react';
import { useQuery, useQueryClient } from 'react-query';
import axios from 'axios';
const baseURL = process.env.api;
async function getSubCategoryListByCategoryId(id) {
// await new Promise((resolve) => setTimeout(resolve, 300));
console.log(`${baseURL}/category/subcategories/${id}`);
try {
const { data } = await axios.request({
baseURL,
url: `/category/subcategories/${id}`,
method: 'get',
});
console.log('data getSubCategoryListByCategoryId index: ', data);
return data;
} catch (error) {
console.log('getSubCategoryListByCategoryId error:', error);
}
}
// const initialState = {
// };
const ProductCreate = () => {
const [values, setValues] = useState(initialState);
const queryClient = useQueryClient();
const { data, isLoading, isError, error, isFetching } = useQuery(
'categoryList',
getPosts
);
const dataList = JSON.parse(data);
useEffect(() => {
setValues({ ...values, categories: dataList });
dataList.map((item) => {
console.log('useEffect values.categories item.id: ', item._id);
queryClient.prefetchQuery(
['subCategoryListByCategoryId', item._id],
getSubCategoryListByCategoryId(item._id)
);
});
}, []);
return <h1>Hello</h1>;
};
export default ProductCreate;
The second parameter to prefetchQuery expects a function that will fetch the data, similar to the queryFn passed to useQuery.
But here, you are invoking the function, thus passing the result of it into prefetchQuery:
getSubCategoryListByCategoryId(item._id)
if you want to do that, you can manually prime the query via queryClient.setQueryData, which accepts a key and the data for that key passed to it.
otherwise, the fix is probably just:
() => getSubCategoryListByCategoryId(item._id)

Use redux-thunk with redux-promise-middleware in the correct way

I'm working in a project with react and redux, I'm enough new so I'm trying to understand better how to use redux-thunk and redux-promise together.
Below you can see my files, in my actions I created a fetch generic function apiFetch() in order to use every time I need to fetch. This function return a promise, that I'm going to resolve in loadBooks(), the code is working and the records are uploaded but when I check the log of the actions I see that the first action is undefined, after there is BOOKS_LOADING, LOAD_BOOKS, BOOKS_LOADING and LOAD_BOOKS_SUCCESS.
I've 2 questions about that:
1) Why is the first action undefined and I've LOAD_BOOKS instead than LOAD_BOOKS_START?
action # 22:54:37.403 undefined
core.js:112 prev state Object {activeBook: null, booksListing: Object}
core.js:116 action function (dispatch) {
var url = './src/data/payload.json';
dispatch(booksIsLoading(true));
return dispatch({
type: 'LOAD_BOOKS',
payload: new Promise(function (resolve) {
…
core.js:124 next state Object {activeBook: null, booksListing: Object}
action # 22:54:37.404 BOOKS_LOADING
action # 22:54:37.413 LOAD_BOOKS
action # 22:54:39.420 BOOKS_LOADING
action # 22:54:39.425 LOAD_BOOKS_SUCCESS
2) If for example the url for the fetch is wrong, I expected to see the action LOAD_BOOKS_ERROR, instead this is the result of the log:
action # 23:06:06.837 undefined action # 23:06:06.837 BOOKS_LOADING
action # 23:06:06.846 LOAD_BOOKS GET
http://localhost:8000/src/data/payldoad.json 404 (Not Found) error
apiFetch Error: request failed at index.js:66 error
TypeError: Cannot read property 'json' of undefined at index.js:90
If I don't use apiFetch(), but normal fetch function, all is working correctly, also the part of the error, with the exception that anyway LOAD_BOOKS is not LOAD_BOOKS_START.
Thank you in advance for any help!
configureStore.js
import { createStore, applyMiddleware, compose, preloadedState } from 'redux';
import reducers from './configureReducer';
import configureMiddleware from './configureMiddleware';
const middleware = configureMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, preloadedState, composeEnhancers(applyMiddleware(...middleware)));
export default store;
actions/index.js
import fetch from 'isomorphic-fetch';
export const booksIsLoading = (bool) => {
return {
type: 'BOOKS_LOADING',
booksLoading: bool,
};
};
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
export const apiFetch = (url) => {
const getPromise = () => (
fetch(url, {
method: 'GET',
})
.then((response) => {
if (response.status !== 200) {
throw Error('request failed');
}
return response;
})
.catch((err) => {
console.log('error apiFetch', err);
// dispatch(fetchBooksError(true));
})
);
return getPromise();
};
export const loadBooks = () => (dispatch) => {
const url = './src/data/payload.json';
dispatch(booksIsLoading(true));
return dispatch({
type: 'LOAD_BOOKS',
payload: new Promise((resolve) => {
delay(2000).then(() => {
apiFetch(`${url}`)
// fetch(`${url}`, {
// method: 'GET',
// })
.then((response) => {
resolve(response.json());
dispatch(booksIsLoading(false));
}).catch((err) => {
console.log('error', err);
});
});
}),
});
};
constants/application.js
export const LOAD_BOOKS = 'LOAD_BOOKS';
reducers/reducer_book.js
import initialState from '../model.js';
import * as types from '../constants/application';
export default function (state = initialState, action) {
switch (action.type) {
case `${types.LOAD_BOOKS}_SUCCESS`: {
console.log('reducer', action.payload);
const data = action.payload.data.items;
const items = Object.values(data);
if (items.length > 0) {
return {
...state,
books: Object.values(data),
booksFetched: true,
booksError: false,
};
}
return state;
}
case `${types.LOAD_BOOKS}_ERROR`: {
return {
...state,
booksError: true,
};
}
case 'BOOKS_LOADING':
return {
...state,
booksLoading: action.booksLoading,
};
default:
return state;
}
}
In which order did you specify middlewares?
Following usage makes action go undefined:
'applyMiddleware(reduxPromiseMiddleware(), reduxThunk)'
Please change the order to: ( thunk first! )
'applyMiddleware(reduxThunk, reduxPromiseMiddleware())'

Categories

Resources