I'm trying to update a specific doc inside firebase v9. It gives me this indexOf error.
Uncaught (in promise) TypeError: n.indexOf is not a function
at rt.fromString (index.esm2017.js:1032:1)
at sa (index.esm2017.js:16038:1)
at actions.js:136:1
at Object.dispatch (index.js:16:1)
at dispatch (<anonymous>:3665:80)
at editFeedback (EditFeedbackPage.js:55:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
at invokeGuardedCallback (react-dom.development.js:4277:1)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:1)
I found some answers suggesting I'm referring to the non-existing doc in db.
To refer to the doc I'm using useParams() to get an id for the document.
In useEffect() I check if the id has a value and it shows that it has, but when I try to pass id into editFeedback() and use it in dispatch it appears to be undefined.
I also tried to store this id inside the state,
const [feedbackId, setFeedbackId] = useState(null)
setFeedbackId(id) // id from params
and then use it inside editFeedback(), but it still was undefined.
I guess it causes the problem and it shows me that indexOf error.
Maybe someone see where can be the issue.
EditFeedbackPage.js
const EditFeedbackPage = () => {
const initialState = {
category: "All",
comments: [],
detail: "",
id: nanoid(),
createdAt: Timestamp.now().toDate(),
status: "Suggestion",
title: "",
upVotesCount: []
}
const ref = useRef()
const [state, setState] = useState(initialState);
const { feedback } = useSelector((state) => state.data);
const { category, detail, title, status } = feedback;
console.log(feedback)
const params = useParams();
const { id } = params;
console.log("id from params => ", id)
// id from params => TexT99K7xz1Q2a4xv5PF
const dispatch = useDispatch();
const navigate = useNavigate();
const cancelAddFeedback = () => {
navigate("/")
}
useEffect(() => {
dispatch(getSingleFeedback(id));
console.log("feedbackId => ", id);
}, []);
const editFeedback = async (e, id) => {
e.preventDefault();
console.log("feedbackId => ", id);
dispatch(editFeedbackInit(id, feedback))
}
const handleInputChange = (e) => {
let { name, value } = e.target;
setState({ ...state, [name]: value })
}
const handleSubmit = (e) => {
e.preventDefault();
setState({ ...state, title: '', detail: "", category: "All" })
}
return (
<EditFeedbackContainer>
<EditFeedbackWholeContainer>
<NavbarFeedback />
<EditFeedbackInnerContainer>
<h2>Edit Feedback</h2>
<EditFeedbackFormContainer
onSubmit={handleSubmit}
>
<h4>Feedback Title</h4>
<label htmlFor="title">Add a short, descriptive headline</label>
<input
ref={ref}
type="text"
name='title'
defaultValue={title}
onChange={handleInputChange}
/>
---------------------
<EditFeedbackButtonsContainer>
<EditFeedbackButtonDelete
// onClick={deleteFeedback}
>
Delete
</EditFeedbackButtonDelete>
<EditFeedbackButtonCancel onClick={cancelAddFeedback}>Cancel</EditFeedbackButtonCancel>
<EditFeedbackButtonAdd
onClick={editFeedback}
>Edit Feedback</EditFeedbackButtonAdd>
</EditFeedbackButtonsContainer>
</EditFeedbackFormContainer>
</EditFeedbackInnerContainer>
</EditFeedbackWholeContainer>
</EditFeedbackContainer >
)
}
actions.js
// edit feedbacks actions
const editFeedbackStart = () => ({
type: types.EDIT_FEEDBACK_START,
});
const editFeedbackSussess = () => ({
type: types.EDIT_FEEDBACK_SUCCESS,
});
const editFeedbackFail = () => ({
type: types.EDIT_FEEDBACK_FAIL,
});
export const editFeedbackInit = (feedback, id, err) => {
return async function (dispatch) {
dispatch(editFeedbackStart());
console.log(id)
// await db.collection("feedbacks").doc(id).update(feedback);
// console.log(id)
// => also tried this version above
const feedDoc = doc(db, "feedbacks", id);
await updateDoc(feedDoc, feedback);
console.log(id)
dispatch(editFeedbackSussess());
if (err) {
dispatch(editFeedbackFail(err))
}
};
}
UPDATED:
Other methods such as add doc, delete doc, get doc, get all docs are working fine. There's only problem only with edit/update doc.
I was trying to ref the doc as I did when delete doc from firebase, which is working fine.
export const deleteFeedbackInit = (id, err) => {
return function (dispatch) {
dispatch(deleteFeedbackStart());
db.collection("feedbacks").doc(id).delete();
dispatch(deleteFeedbackSussess());
if (err) {
dispatch(deleteFeedbackFail(err))
}
};
}
i have an async thunk function that gets a user from database and sets it to the state, but i want to set the user to local storage after getting it,
//SignIn.tsx
const userState = useAppSelector((state) => state.user);
const handleSignIn = async (e: React.SyntheticEvent) => {
e.preventDefault();
const user = await dispatch(signIn({ email, password }));
localStorage.setItem('currentUser', JSON.stringify(userState));
console.log(userState);
navigate('/shop');
};
//userSlice.ts
export const signIn = createAsyncThunk(
'user/signIn',
async ({ email, password }: { email: string; password: string }) => {
const { user }: UserCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const data = await getUser(user);
return { data, user };
}
);
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, action) => {
state.email = action.payload.user.email;
state.token = action.payload.user.refreshToken;
state.id = action.payload.user.uid;
state.avatarUrl = action.payload.data.avatarUrl;
state.lastName = action.payload.data.lastName;
state.name = action.payload.data.name;
});
}
im awaiting the dispatch and then trying to set the user to local storage using updated state, idk why the state doesnt update after awaiting the dispatch, instead of updated state im setting the PREVIOUS state to the local storage, please help, im struggling with this for an hours
Actually, you are storing current state in local storage, you have to store fetched user
const { user } = await dispatch(signIn({ email, password }));
localStorage.setItem('currentUser', user);
Or you can store user directly in createAsync function
You can unwrap and then store
const onClick = () => {
dispatch(fetchUserById(userId))
.unwrap()
.then((originalPromiseResult) => {
localStorage.setItem('currentUser', JSON.stringify(originalPromiseResult));
})
.catch((rejectedValueOrSerializedError) => {
// handle error here
})
}
So, I am trying to update category data using useMutation hook in react. The fields I am going to mutate are name, description with it's associated id. But whenever I send mutation I got Missing field 'editScat' while writing result true error. Here is my code.
const [categoryName, setCategoryName] = useState("");
const [categoryDescription, setCategoryDescription] = useState("");
const [categoryId, setCategoryId] = useState("");
const EDIT_SUB_CATEGORY = gql`
mutation editScat($id: UUID!, $name: String!, $description: String!) {
editScat(id: $id, name: $name, description: $description) {
payload {
id
name
description
}
}
}
`;
const [updateCatMutation] = useMutation(EDIT_SUB_CATEGORY);
const updateMe = (e) => {
e.preventDefault();
updateCatMutation({
variables: {
id: categoryId,
name: categoryName,
description: categoryDescription,
},
optimisticResponse: true,
})
.then((res) => {
alert("Success");
})
.catch((err) => {
alert("Failed");
});
};
What did I missed here?
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.
My goal is to:
Load one document and get the value for "account"
Then load a new document with "account" as one of the document names in firestore. I am using this.account for the path
<script>
// eslint-disable-next-line
import firestore from "firestore";
// eslint-disable-next-line
import NewEmployee from "#/components/updatePopups/NewEmployee";
import db from "#/components/fbInit";
import firebase from "firebase";
export default {
// eslint-disable-next-line
components: { NewEmployee },
data() {
return {
account: "",
users: [],
acc:[],
};
},
computed: {
computed() {
const user = firebase.auth().currentUser;
const info = [];
db
.collection("userProfiles")
.doc(user.uid)
.get()
.then(doc => {
const doc1content = doc.data();
return doc1content.account;
console.log(doc1content.account)
});
}
},
created() {
const user = firebase.auth().currentUser;
let account = computed();
let empRef = db.collection('userProfiles').doc(this.account).collection('employees');
let empCollection = empRef.get()
.then(snapshot => {
this.users = [];
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
const data = {
id: doc.id,
Name: doc.data().Name,
GroupHex: doc.data().GroupHex,
DeviceType: doc.data().DeviceType,
UserIDInt: doc.data().UserIDInt,
SuperID: doc.data().SuperID,
misc: doc.data().misc,
Status: doc.data().Status,
};
this.users.push(data)
});
})
},
};
</script>
I keep getting undefined errors or value can't be "" or """
account is undefined
ReferenceError: computed is not defined
The reason I am not doing this all in one created hook is because I would like to use the snapshot feature to reload the page when there is a change automatically and I had trouble doing that when it was nested inside another doc.get(). I would like to have the "account" variable loaded and stored when the page is loaded(or before).
Any help would be greatly appreciated.