I am busy with making a booking function in vue3/vuex.
A user can book an item and also remove it from the basket.The problem is that the Filter function in vue does not remove the object in the array and I can not find out what the problem is. I hope you can help me
This is the result if I put console.log() in the removeFromBasket(state, payload)
removeFromBasket(state, payload) {
console.log('removeFromBasket', payload, JSON.parse(JSON.stringify(state.basket.items)))
}
method to remove
removeFromBasket() {
this.$store.commit('basket/removeFromBasket', this.bookableId);
}
basket module
const state = {
basket: {
items: []
},
};
const getters = {
getCountOfItemsInBasket: (state) => {
return state.basket.items.length
},
getAllItemsInBasket: (state) => {
return state.basket.items
},
inBasketAlready(state) {
return function (id) {
return state.basket.items.reduce((result, item) => result || item.bookable.id === id, false);
}
},
};
const actions = {};
const mutations = {
addToBasket(state, payload) {
state.basket.items.push(payload);
},
removeFromBasket(state, payload) {
state.basket.items = state.basket.items.filter(item => item.bookable.id !== payload);
}
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
I have solved the problem.
I used typeof() in console.log() to see what type the payload and item.bookable.id are.
The payload was a string and the item.bookable.id was a number.
So I put the payload in parseInt(payload) and the problem was solved.
removeFromBasket(state, payload) {
state.basket.items = state.basket.items.filter(item => item.bookable.id !== parseInt(payload));
}
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.
I want to store image to redux store,
but it still give me empty object.
When I console.log the image, it has the following specification
here's my code
onClickSave = () => {
if (this.editor) {
// If you want the image resized to the canvas size (also a HTMLCanvasElement)
const canvas = this.editor.getImageScaledToCanvas();
canvas.toBlob((blob) => {
const file = new File([blob], "my-file", {
type: "image/png",
});
console.log(file) // return the object in the picture above
this.props.handlePicture(file); // trying store to redux, but failed
});
}
};
heres my action
const mapDispatchToProps = (dispatch) => {
return {
handlePicture: file => {
return dispatch({type: PRODUCT_ADD_PICTURE, payload: file })
}
}
}
My reducer
// import things
const initialState = {
name: "",
description: "",
picture: null,
quantity: 0,
};
const productReducer = (state = initialState, action) => {
const payload = action.payload
switch (action.type) {
// other code
case PRODUCT_ADD_PICTURE: {
console.log('here paylaod', payload)
return { ...state, picture: payload };
}
// other code
default:
return state;
}
};
Heres log result in the reducer.
Usually when I store it to redux it gives me some string, and ready to send it to the server.
Why it is return empty?
Any help would be appreciated.
Recently started using Hooks and, as cook as they are, they are giving me a bit of a headache.
I have a custom useFetch() hook that deals with fetching data from the API.
I also have a component where I need to use useFetch a few times and the results must be passed from one to another.
E.g.:
const ComponentName = () => {
const { responseUserInfo } = useFetch('/userinfo')
const { responseOrders } = useFetch(`/orders?id=${responseUserInfo.id}`)
const { isOrderRefundable } = useFetch(`/refundable?id={responseOrders.latest.id}`)
return <div>{isOrderRefundable}</div>
}
So, how do I actually "cascade" the hooks without creating 3 intermediate wrappers? Do I have to use HoC?
Your hook could return a callback, that when called does the API call:
const [getUserInfo, { userInfo }] = useFetch('/userinfo');
const [getOrders, { orders }] = useFetch(`/orders`)
const [getOrderRefundable, { isOrderRefundable }] = useFetch(`/refundable`);
useEffect(getUserInfo, []);
useEffect(() => { if(userInfo) getOrders({ id: userInfo.id }); }, [userInfo]);
useEffect(() => { if(orders) getOrderRefundable({ id: /*..*/ }); }, [orders]);
But if you always depend on the whole data being fetched, I'd just use one effect to load them all:
function useAsync(fn, deps) {
const [state, setState] = useState({ loading: true });
useEffect(() => {
setState({ loading: true });
fn().then(result => { setState({ result }); });
}, deps);
return state;
}
// in the component
const { loading, result: { userInfo, orders, isRefundable } } = useAsync(async function() {
const userInfo = await fetch(/*...*/);
const orders = await fetch(/*...*/);
const isRefundable = await fetch(/*...*/);
return { userInfo, orders, isRefundable };
}, []);
I have an array of objects i.e queueDetails[{},{}]. I have another array of ids from response
"payload":[{"id":"1"},{"id":"2"}].
I want to filter out the ids in payload from queueDetails for which I have following code:
action.payload.map(payload => {
state.queueDetails.filter(queue => queue._id !== payload.id)
})
return {
...state,
queueDetails: ???
}
How do I proceed from here.
I think, it's safe to guess, you're building part of Redux store reducer, if that's the case, corresponding case section for filtering action may be something, like:
case FILTER_QUEUE_DETAILS : {
const { queueDetails } = state,
{ payload } = action,
submittedIds = payload.map(({id}) => id)
return {...state, queueDetails: queueDetails.filter(({id}) => !submittedIds.includes(id))}
}
You may find the quick demo below:
const { createStore } = Redux
const defaultState = {queueDetails:[{id:1,data:'somedata'},{id:2,data:'moredata'},{id:3,data:'somemore'}]},
FILTER_QUEUE_DETAILS = 'FILTER_QUEUE_DETAILS',
appReducer = (state=defaultState, action) => {
switch(action.type) {
case FILTER_QUEUE_DETAILS : {
const { queueDetails } = state,
{ payload } = action,
submittedIds = payload.map(({id}) => id)
return {...state, queueDetails: queueDetails.filter(({id}) => !submittedIds.includes(id))}
}
default: return state
}
},
store = createStore(appReducer)
//initial state
console.log(`// initial state:\n`, store.getState())
//dispatch action to filter out id's 1, 3
store.dispatch({
type: FILTER_QUEUE_DETAILS,
payload: [{id:1},{id:3}]
})
//log resulting state
console.log(`// state upon id's 1 and 3 filtered out:\n`,store.getState())
.as-console-wrapper {min-height:100%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>