I'm trying to build a simple budgeting app.
Whenever I insert this model into my app. I get a proxy for the expenses. Where is the flaw in my thinking?
I have an action on the Budget.js
when I print it in the useEffect this is what console.log outputs for the expenses a proxy.
I'm expecting it to print the actual data from the initial state.
React.useEffect(() => {
budget.addDummyData()
console.log(budget.expenses)
}, [])
[[Handler]]: Object
[[Target]]: Array(0)
[[IsRevoked]]: false
//////////////////////////////////////////////////////////////////////
//SubCategory
const SubCategory = types
.model('SubCategory', {
id: types.maybeNull(types.string, ''),
name: types.maybeNull(types.string, ''),
amount: types.maybeNull(types.number, 0)
})
const SubCategoryStore = types.model({ subCategory: types.optional(SubCategory, {}) })
export default SubCategoryStore
/////////////////////////////////////////////////////////////////////////
//Category.js
const Category = types
.model('Category', {
id: types.maybeNull(types.string, ''),
name: types.maybeNull(types.string, ''),
subCategories: types.array(SubCategory)
})
const CategoryStore = types.model({ category: types.optional(Category, {}) })
export default CategoryStore
///////////////////////////////////////////////////////////////
// Budget
const Budget = types
.model('Budget', {
totalIncome: 200,
expenses: types.array(Category)
// incomes: types.optional(types.array(Category), [])
}).actions({
addDummyData() {
self.expenses.push(initialStateExpenses)
}
})
const BudgetStore = types.model({ budget: types.optional(Budget, {}) })
export default BudgetStore
const initialStateExpenses = {
id: '123',
name: 'Food',
subCategories: [
{
id: '1314',
name: 'Grocery',
amount: 250
},
{
id: '1442',
name: 'Restaurants',
amount: 50
}
]
}
expenses is of type Category[], you are passing an object. I assume you want to set the expenses from subCategories. If so you can try this
addDummyData() {
initialStateExpenses.subCategories.forEach(ex => self.expenses.push(ex))
}
or
addDummyData() {
self.expenses = initialStateExpenses.subCategories
}
A better approach would be to pass the initialStateExpenses via args to the addDummyData function so your model doesn't depend on external variables
addDummyData(initialStateExpenses) {
initialStateExpenses.subCategories.forEach(ex => self.expenses.push(ex))
}
or
addDummyData(initialStateExpenses) {
self.expenses = initialStateExpenses.subCategories
}
then use it like
budget.addDummyData(initialStateExpenses)
Related
I have an a state object in React that looks something like this (book/chapter/section/item):
const book = {
id: "123",
name: "book1",
chapters: [
{
id: "123",
name: "chapter1",
sections: [
{
id: "4r4",
name: "section1",
items: [
{
id: "443",
name: "some item"
}
]
}
]
},
{
id: "222",
name: "chapter2",
sections: []
}
]
}
I have code that adds or inserts a new chapter object that is working. I am using:
// for creating a new chapter:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters,
newChapter // insert new object
]
}
})
And for the chapter update, this is working:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters.map(ch => {
return ch.id === selectedChapterId
? {...ch, name: selectedChapter.name}
: ch
})
]
}
})
But for my update/create for the sections, I'm having trouble using the same approach. I'm getting syntax errors trying to access the sections from book.chapters. For example, with the add I need:
// for creating a new section:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters,
...old.chapters.sections?
newSection // how to copy chapters and the sections and insert a new one?
]
}
})
I know with React you're supposed to return all the previous state except for what you're changing. Would a reducer make a difference or not really?
I should note, I have 4 simple lists in my ui. A list of books/chapters/sections/items, and on any given operation I'm only adding/updating a particular level/object at a time and sending that object to the backend api on each save. So it's books for list 1 and selectedBook.chapters for list 2, and selectedChapter.sections for list 3 and selectedSection.items for list 4.
But I need to display the new state when done saving. I thought I could do that with one bookState object and a selectedThing state for whatever you're working on.
Hopefully that makes sense. I haven't had to do this before. Thanks for any guidance.
for adding new Section
setSelectedBook( book =>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
selectedChapter.sections=[...selectedChapter.sections, newSection ]
return {...book}
})
For updating a section's name
setSelectedBook(book=>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )
selectedSection.name = newName
return {...book}
})
For updating item's name
setSelectedBook(book =>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )
let selectedItem = selectedSection.items.find(itm => itm.id === selectedItemId)
selectedItem.name = newItemName
return {...book}
})
I hope you can see the pattern.
I think the map should work for this use case, like in your example.
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters.map(ch => {
return { ...ch, sections: [...ch.sections, newSection] }
})
]
}
})
In your last code block you are trying to put chapters, sections and the new section into the same array at the same level, not inside each other.
Updating deep nested state objects in React is always difficult. Without knowing all the details of your implementation, it's hard to say how to optimize, but you should think hard about different ways you can store that state in a flatter way. Sometimes it is not possible, and in those cases, there are libraries like Immer that can help that you can look in to.
Using the state object you provided in the question, perhaps you can make all of those arrays into objects with id for keys:
const book = {
id: "123",
name: "book1",
chapters: {
"123": {
id: "123",
name: "chapter1",
sections: {
"4r4": {
id: "4r4",
name: "section1",
items: {
"443": {
id: "443",
name: "some item"
}
}
}
}
},
"222": {
id: "222",
name: "chapter2",
sections: {},
}
]
}
With this, you no longer need to use map or find when setting state.
// for creating a new chapter:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[newChapter.id]: newChapter
}
}
})
// for updating a chapter:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[selectedChapter.id]: selectedChapter,
}
}
})
// for updating a section:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[selectedChapter.id]: {
...selectedChapter,
sections: {
[selectedSectionId]: selectedSection
}
},
}
}
})
Please let me know if I misunderstood your problem.
I have this Redux store and reducer
const INITIAL_WORK = {
departments: []
}
const works = (state = INITIal_WORK, action) => {
switch(action.type){
case 'ADD_DEPARTMENT':
return {
...state,
departments: [...state.department, action.item]
}
default:
return state
}
}
In departments work people so I want to this people was inside a single department works people. So after fetch data from db I want my store look like this:
const INITIAL_WORK = {
departments: [
{
id: 1,
name: "First department",
people: [
{
id: 1,
name: "Johna Wayne"
},
{
id: 2,
name: "Jessica Biel"
},
{
id: 3,
name: "Bratt Pitt"
}
]
},
{
id: 2,
name: "second department",
people: [
{
id: 4,
name: "Salma Hayek"
},
{
id: 5,
name: "Sylvester Stallone"
}
]
}
]
}
Is it possible create case in reducer which will be added people inside people array inside single department? How can I do that?
Yes, absolutely.
Let us assume your API returns payload as following.
{
departmentId: `id of the department`
id: `person id`
name: `person name`
}
const INITIAL_WORK = {
departments: []
}
const works = (state = INITIal_WORK, action) => {
switch(action.type){
case 'ADD_DEPARTMENT':
return {
...state,
departments: [...state.department, action.item]
}
case 'ADD_PERSON':
const { departmentId, id, name } = action.item
const departments = [...state.departments];
departments[departmentId].people.push({id, name})
return {
...state,
departments
}
default:
return state
}
}
Combining Immer.js and Redux will be helpful for this case.
Here is a simple example of the difference that Immer could make in practice.
// Reducer with inital state
const INITAL_STATE = {};
// Shortened, based on: https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/products.js
const byId = (state = INITAL_STATE, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
return {
...state,
...action.products.reduce((obj, product) => {
obj[product.id] = product
return obj
}, {})
}
default:
return state
}
}
After using Immer, our reducer can be expressed as:
import produce from "immer"
// Reducer with inital state
const INITAL_STATE = {};
const byId = produce((draft, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
action.products.forEach(product => {
draft[product.id] = product
})
}
}, INITAL_STATE)
let mergePeople=[];
INITIAL_WORK.departments.filter((cur,index)=>{
cur.people.filter((person)=>{
mergePeople.push(person);
})
}
)
console.log(mergePeople);
I have API response coming like below, I need to filter it out so that I should only get few fields out of this. e.g. I just need description and placeLocation.
results: [{placeId: "BHLLC", placeLocation: "BUFR", locationType: "BUFR",…},…]
0: {placeId: "BHLLC", placeLocation: "BUFR", locationType: "BUFR",…}
binControl: "Y"
description: "BUFR - Good Stock"
locationType: "BUFR"
ours: "Y"
placeId: "BHLLC"
placeLocation: "BUFR"
transferIsUse: "Y"
usable: "F"
I need to pass the fields in body inside the fetch.js file like below
const body = {
criteria,
fields: ["description", "placeLocation", "whosPlace"],
skip: 0,
take: 2000
};
And then I am trying to map the results like this. Please note that places is the result of the fetches call to API calling function.
let result = [];
if (places) {
result = places.results.map(p => ({
placeId: p.description, name: p.placeLocation
}));
}
the fetch function from fetch file look like below
export const fetchPlacesTo = async () => {
const criteria = [
{
anyOf: [
{
field: "WhosPlace",
value: "L%",
operator: "li"
}
]
}
];
const body = {
criteria,
fields: ["description", "placeLocation", "whosPlace"],
skip: 0,
take: 2000
};
const result = sessionManager
.refreshFetch(
`${**********_API_URI}/place/place`,
fetchUtils.hwsGetRequest(body)
)
.then(parseResponse);
return result;
};
And the function which calls this is below
export const getPlacesTo = () => async dispatch => {
try {
dispatch(loading.incrementLoading());
const places = await fetches.fetchPlacesTo().catch(error => {
console.log(error); //CRS: Change to logger
dispatch(loading.setWarning(errors.ERROR_GET_DATA));
});
let result = [];
if (places) {
result = places.results.map(p => ({
placeId: p.description, name: p.placeLocation
}));
}
dispatch({
type: constants.GET_LOOKUP_ENTITY,
payload: {
name: "placeIdToRepair",
value: result
}
});
I guess you want placeId and placeLocation froom response into result
var result=[]
var data={results: [{placeId: "BHLLC", placeLocation: "BUFR", locationType: "BUFR"},{placeId: "BHALC", placeLocation: "BAFR", locationType: "BAFR"},{placeId: "BHBLLC", placeLocation: "BUBFR", locationType: "BUBFR"}]}
data.results.map(p=>result.push({
placeId: p.placeId, name: p.placeLocation
}));
console.log(result)//(3) [{…}, {…}, {…}]
0:
placeId: "BHLLC"
name: "BUFR"
__proto__: Object
1:
placeId: "BHALC"
name: "BAFR"
__proto__: Object
2:
placeId: "BHBLLC"
name: "BUBFR"
__proto__: Object
length: 3
I'm trying to create a set of reducers in order to change an attribute of all objects in a nested list.
The input payload looks like the following:
const payload = [
{
name: "Peter",
children: [
{
name: "Sarah",
children: [
{
name: "Sophie",
children: [
{
name: "Chris"
}
]
}
]
}
]
}
];
I now want to change the name attribute of all elements and child elements.
const mapJustNickname = elem => {
return {
...elem,
nickname: elem.name + "y"
};
};
How do I use this map function recursively on all child elements?
I found a way to do this by putting the the recursion within the same mapping function.
const mapToNickname = (elem) => {
return {
nickname: elem.name +'y',
children: elem.children && elem.children.map(mapToNickname)
}
}
console.log(payload.map(mapToNickname));
But I'd like to have the mapping of the name separated from the recursion (for reasons of keeping the mapping functions as simple as possible) and being able to chain them later. Is it somehow possible to do this with two reducers and then chaining them together?
Let's start by rigorously defining the data structures:
data Person = Person { name :: String, nickname :: Maybe String }
data Tree a = Tree { value :: a, children :: Forest a }
type Forest a = [Tree a]
type FamilyTree = Tree Person
type FamilyForest = Forest Person
Now, we can create mapTree and mapForest functions:
const mapTree = (mapping, { children=[], ...value }) => ({
...mapping(value),
children: mapForest(mapping, children)
});
const mapForest = (mapping, forest) => forest.map(tree => mapTree(mapping, tree));
// Usage:
const payload = [
{
name: "Peter",
children: [
{
name: "Sarah",
children: [
{
name: "Sophie",
children: [
{
name: "Chris"
}
]
}
]
}
]
}
];
const mapping = ({ name }) => ({ name, nickname: name + "y" });
const result = mapForest(mapping, payload);
console.log(result);
Hope that helps.
Create a recursive map function that maps an item, and it's children (if exists). Now you can supply the recursiveMap with a ever transformer function you want, and the transformer doesn't need to handle the recursive nature of the tree.
const recursiveMap = childrenKey => transformer => arr => {
const inner = (arr = []) =>
arr.map(({ [childrenKey]: children, ...rest }) => ({
...transformer(rest),
...children && { [childrenKey]: inner(children) }
}));
return inner(arr);
};
const mapNickname = recursiveMap('children')(({ name, ...rest }) => ({
name,
nickname: `${name}y`,
...rest
}));
const payload = [{"name":"Peter","children":[{"name":"Sarah","children":[{"name":"Sophie","children":[{"name":"Chris"}]}]}]}];
const result = mapNickname(payload);
console.log(result)
I'm using Vuex to show a list of users from 'store.js'. That js file has array like this.
var store = new Vuex.Store({
state: {
customers: [
{ id: '1', name: 'user 1',},
]
}
})
I want to insert a new set of values to the same array
{ id: '1', name: 'user 1',}
The above values are obtained from a URL (vue-resource). Below is the code to push the obtained data to the array. However, the data is not inserting
mounted: function() {
this.$http.get('http://localhost/facebook-login/api/get_customers.php')
.then(response => {
return response.data;
})
.then(data => {
store.state.customers.push(data) // not working!!
console.log(data) // prints { id: '2', name: 'User 2',}
store.state.customers.push({ id: '2', name: 'User 2',})
});
}
You are trying to modify the vuex state from the vue component, You can not do it. You can only modify vuex store from a mutation
You can define a mutation like following:
var store = new Vuex.Store({
state: {
customers: [
{ id: '1', name: 'user 1',},
]
},
mutations: {
addCustomer (state, customer) {
// mutate state
state.customers.push(customer)
}
}
})
Now you can commit this mutation from the vue instance, like following:
mounted: function() {
this.$http.get('http://localhost/facebook-login/api/get_customers.php')
.then(response => {
return response.data;
})
.then(data => {
store.commit('addCustomer', { id: '2', name: 'User 2'})
});
}