redux reselect selectors for relational data - reactjs

selectors takes 2 arguments, state and props, but how can we handle selectors for relational data ?
initialState = {
department :{ids:[1],byId:{name:'dep 1',id:1,parent:null}}
sections :{ids:[2],byId:{name:'section 1.1',id:2,parent:1}}
subsections :{ids:[3],byId:{name:'subsection 1.1.1',id:3,parent:2}}
}
here department is aparent of n(sections) and grandparent of n(subsections)
in <Department id="1" /> i want to select department 1 and all its children.
how can i write such selector without passing the id of the department i need to the selector ?

You might use a router (react-router) to pass such keys by url segments, then you get access to the ids you need, ie:
mapStateToProps(state, ownProps) {
// then get access to id here
console.log(ownProps.params.department_id);
}

Here you'll find an implementation that - i hope - will answer to your first question : "how can we handle selectors for relational data ?"
import { createSelector } from "reselect";
const state = {
departments: [{ id: 1, byId: { name: "dep 1", id: 1, parent: null } }],
sections: [{ id: 2, byId: { name: "section 1.1", id: 2, parent: 1 } }],
subsections: [
{
id: 3,
byId: { name: "subsection 1.1.1", id: 3, parent: 2 }
}
]
};
const clone = obj => JSON.parse(JSON.stringify(obj));
const bottomUp = (...levels) =>
levels.reduce((children, parents) => {
const p = clone(parents);
const addToParent = child => {
const parent = p.find(par => par.id === child.byId.parent);
if (parent) {
if (parent.children) {
parent.children.push(child);
} else {
parent.children = [child];
}
}
}
children.forEach(addToParent);
return p;
});
const selectSubs = state => state.subsections;
const selectSecs = state => state.sections;
const selectDeps = state => state.departments;
const selectHierarchy = createSelector(
selectSubs,
selectSecs,
selectDeps,
bottomUp
);
console.log(selectHierarchy(state));
This new selector will return :
As you can see, since I didn't fully understand your store structure, i performed few changes in it : each domain became an array and each id became a number.
I hope it will help ...
Cheers

Related

React nested items rerender not working as expected

im trying to develop component that will sort new items, depending if item parent is or is not present on the list already. Components can be nested in each other to unlimited depth. Parent have list of children, children have parentId. Now, it works as expected at the first render, but when new item appear on the list (its added by user, using form, up in the structure), it does in fact make its way to components list, but is not shown on the screen until page reload. I can see temporary list that is used to make all calculations have the item as expected in the nested structure. Then i set state list to value of temp, but its not working, and i dont know why. Im quite new to react stuff. In act of desperation i even tried to destructure root parent of the item, hoping it will force rerender, but that didnt worked too. Anybody could help with this?
http://jsfiddle.net/zkfj03um/13/
import React, { useState } from 'react';
function Component(props) {
const [component, setComponent] = useState(props.component);
return (
<div>
{component.id};
{component.name};
<ul>
{component.subcomps && component.subcomps.map((comp) =>
<li key={comp.id} style={{ textAlign: 'left' }}>
<Component component={comp}
id={comp.id}
name={comp.name}
parentId={comp.parentId}
subcomps={comp.subcomps}
/>
</li>)}
</ul>
</div>
);
}
function ComponentsList(props) {
const newComponents = props.newComponents;
const [filteredComponents, setFilteredComponents] = useState();
function deepSearch(collection, key, value, path=[]) {
for (const o of collection) {
for (const [k, v] of Object.entries(o)) {
if (k === key && v === value) {
return {path: path.concat(o), object: o};
}
if (Array.isArray(v)) {
const _o = deepSearch(v, key, value, path.concat(o));
if (_o) {
return _o;
}
}
}
}
}
async function filter() {
let temp = [];
await newComponents.forEach((comp) => {
//parent may be, or may not be on the list. Its not necesary
const parentTuple = deepSearch(filteredComponents, 'id', comp.parentId);
if (!parentTuple) {
//create parent substitute logic
} else {
const parent = parentTuple.object;
const root = parentTuple.path[0];
const mutReplies = [comm, ...parent.replies];
parent.replies = mutReplies;
temp = [{...root}, ...temp]
}
})
setFilteredComponents([...temp])
}
useEffect(() => {
setLoading(false);
}, [filteredComponents]);
useEffect(() => {
setLoading(true);
filter();
}, [newComponents]);
return (<>
{!loading && filteredComponents.map((component, index) =>
<li key={index}>
<Component component={component} />
</li>
)}
</>);
}
const items = [
{ id: 1, name: 'sample1', subcomps: [{ id: 5, name: 'subcomp1', parentId: 1, subcomps: [] }] },
{
id: 2, name: 'sample2', subcomps: [
{ id: 6, name: 'subcomp2', subcomps: [], parentId: 2 },
{ id: 7, name: 'subcomp3', subcomps: [], parentId: 2 }
]
},
]
ReactDOM.render(<ComponentsList newComponents={items} />, document.querySelector("#app"))

Replacing value in nested state with react functional components

I am trying to use update state in a react function component but it is not working. I tried following a tutorial on pluralsite and apply it to my own project. Ideally this code should be finding the product based on the ID number and replacing the total with a new value.
Unfortunately I am getting an error saying that 'productData.find' is not a function and I'm not sure where the code being used for that is. Are there any suggestions on how to solve this issue?
This is what the data looks like. In this example I am saving the first element of the array.
export let data = [
{
name: "Name",
description:
"",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
...
];
function CompareCard({}) {
const index = 0;
const [productData, setProductData] = useState(data[index]);
function setTotalUpdate(id) {
const productPrevious = productData.find(function (rec) {
return rec.id === id;
});
const productUpdated = {
...productPrevious,
total: 1,
};
const productNew = productData.map(function (rec) {
return rec.id === id ? productUpdated : rec;
});
setProductData(productNew);
}
setTotalUpdate(1)
}
It's because productData is not an array so .find would not work. You want iterate over the products property in your data, so do productData.products.find(...)
When you do
const [productData, setProductData] = useState(data[index])
you don't pass an Array on your state but an Object (the first element of your data so an Object) and Object don't have find method.
Try
const [productData, setProductData] = useState([data[index]])
with [] on our useState to put your Object on array
///////////////////////////////// Edit /////////////
Ok, I try your code, and I propose you this.
import React, { useState } from "react";
const data = [
{
name: "Name",
description: "",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
];
const CompareCard = () => {
// setState with the subArray products from data[0], I use '...' (spread operator) inside a Array or an Object to make a shalow copy
const [productsData, setProductsData] = useState([...data[0].products]);
const setTotalUpdate = (id) => {
// find the product to change inside products collection, that's ok
const productPrevious = productsData.find((rec) => {
return rec.id === id;
});
// create a new product to change one property's value, you can do something like 'productPrevious.total = 1', same same
const productUpdated = {
...productPrevious,
total: 1,
};
// create a new products collection to update state
const productNew = productsData.map((rec) => {
return rec.id === id ? productUpdated : rec;
});
setProductsData([...productNew]);
};
const setTotalUpdateSecond = (id) => {
// create a newState
const newState = productsData.map((product) => {
// condition if id === productId and do something
if (id === product.id) {
product.total = 1;
}
// both case, I return the product (change or not)
return product;
});
setProductsData([...newState]);
};
return (
<>
<button onClick={() => setTotalUpdate(1)}>Test old method on product 1</button>
<button onClick={() => setTotalUpdateSecond(2)}>Test second method on product 2</button>
{productsData.map((product) => {
return (
<>
<p>Product Id : {product.id} / Product Total : {product.total}</p>
</>
);
})}
</>
);
};
export default CompareCard;
Can you copy / past this, try and say me if it's what you want, if yes, I explain you where the confusion was. If not, explain me, what's the problem here and I modificate.

State not updating in react with a nested object (hooks)

I've been playing with react-beautiful-dnd and hooks (very new to react) - and for some reason my state doesn't update on drag. (Edit: I know the logic only works for 'same category' drag - this isn't updating the UI either for me)
Data (simplified)
const skills = {
skills: {
skill1: {
id: "skill1",
name: "Communication"
},
skill2: {
id: "skill2",
name: "Empathy"
},
skill3: {
id: "skill3",
name: "Initiative"
}
},
categories: {
cat1: {
id: "cat1",
name: "Core",
skillIds: ["skill1", "skill2", "skill3", "skill4"]
},
cat2: {
id: "cat2",
name: "Craft",
skillIds: ["skill5", "skill6", "skill7", "skill8"]
},
cat3: {
id: "cat3",
name: "Leadership",
skillIds: ["skill9", "skill10"]
}
},
categoryOrder: ["cat1", "cat2", "cat3"]
};
Function to update the skillIds array in the correct category
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const onDragEnd = (result) => {
const { source, destination } = result;
// dropped outside the list
if (!destination) {
return;
}
// Handle moving within one category
if (source.droppableId === destination.droppableId) {
const catSkills = data.categories[source.droppableId].skillIds;
const items = reorder(catSkills, source.index, destination.index);
const newData = {
...data,
categories: {
...data.categories,
[source.droppableId]: {
...data.categories[source.droppableId],
skillIds: items
}
}
};
setData(newData);
}
};
I've created a simplified codesandbox to test - https://codesandbox.io/s/hooks-problem-il5m4
Any help appreciated!
I can see the state is getting updated
if (source.droppableId === destination.droppableId) { setData(data) }
this "if" clause means it will only update the state if the drag is happen on the same lane .i think you have tried to drag it to another lane . hope this is what you meant
Edit: I have understood your problem . The issue is you are not using updated data you are looping the static skill.I hope this solve your problem
{data.categoryOrder.map((catId) => {
const category = data.categories[catId]; //change skills to data
const catSkills = category.skillIds.map(
(skillId) => skills.skills[skillId]
);

Functional component problems React

I transformed a class component into a functional component but it looks like it does not work in a way it suppose to work and I can not find what is wrong. When I create a new object there is no name for the object and when I try to mark the object as a complete it removes all created objects at ones. I created a codesandbox here. Unfortunately, I am not too much familiar with functional component. Any help would be appreciated.
Here is my codesandbox sample:
https://codesandbox.io/s/crazy-sid-09myu?file=/src/App.js
Your Todos:
const [todos, setTodos] = useState([
{ id: uuid(), name: "Task 1", complete: true },
{ id: uuid(), name: "Task 2", complete: false }
]);
onAddHandler:
const addTodo = () =>
setTodos([...todos, { id: uuid(), name: "New Task", complete: false }]);
onSetCompleteHandler:
const setCompleteHandler = id =>
setTodos(
todos.map(todo => {
if (todo.id === id) {
return {
...todo,
complete: todo.complete ? 0 : 1
};
}
return todo;
})
);
I have created your new todos. Check out this link
Todos App
I have updated your code, please check the URL https://codesandbox.io/s/determined-morning-n8lgx?file=/src/App.js
const onComp = id => {
for (let i = 0; i < todos.length; i++) {
if (todos[i].id === id) {
let t = { ...todos[i] };
t.complete = !t.complete;
todos[i] = t;
}
}
setTodos([...todos]); // Here todos reference need to be changed
};
And also
const onSubmit = event => {
event.preventDefault();
setTodos([
...todos,
{
id: generateNewId(),
name: newTodoName,
complete: false
}
]);
setNewTodoName("");
};
While using hooks we need to be careful about state variable updates. While manipulating arrays and objects use the spread operator to create new references which invokes child components and re-render current component.

Hooks and immutable state

I am apologising in advance if this questions has already been answered before (and also for the long post, but I've tried to be as specific as I could be). But, the answers I found does not completely satisfy me.
I want to use the new amazing React Hooks for my project. And for what I've been doing so far, it has been straight forward.
Now I have ran into a more complex example, and I feel unsure on how I best should tackle this one.
Let's say, I have more of a complex object (at least it's not flat) in my state.
{
persons: [
{
id: 1,
name: 'Joe Doe',
age: 35,
country: 'Spain',
interests: [
{ id: 1, en: 'Football', es: 'FĂștbol' },
{ id: 2, en: 'Travelling', es: 'Viajar' }
]
},
{
id: 2,
name: 'Foo Bar',
age: 28,
country: 'Spain',
interests: [
{ id: 3, en: 'Computers', es: 'Computadoras' },
{ id: 4, en: 'Cooking', es: 'Cocinar' }
]
}
],
amount: 2,
foo: 'bar'
}
What is the best way to:
Add an item (an object) to my colleagues array
Add an item to a specific "interests" array?
Manipulate the value of a property within an object in the array?
Change a value outside the persons array, for example foo?
using the useState hook?
The following code examples will try to illustrate each question. They're not tested...
Let us consider I have a container that begins with this.
It also includes the functions that are split up in the rest of the post.
const [colleagues, setColleagues] = useState({});
// using the useEffect as "componentDidMount
useEffect(() => {
// receiving data from ajax request
// and set the state as the example object provided earlier
setColleagues(response.data);
}, []);
1) So for the first question. Is this valid?
Or do I need to to make sure that each and every object in my persons array is destructured?
const onAddNewColleague = () => {
// new colleague, this data would be dynamic
// but for the sake of this example I'll just hard code it
const newColleague = {
name: 'Foo Baz Bar',
age: 30,
country: 'Spain',
interests: [
{ en: 'Cats', es: 'Gatos' }
]
};
// creates a copy of the state
// targets the "persons" prop and adds a new array
// where the new colleague is appended
const newState = {
...colleagues,
persons: colleagues.concat(newColleague)
};
// updates the state
setColleagues(newState);
};
2) This feels wrong as I end up updating the entire persons array instead of just the interest array for a specific person.
const onAddNewInterest = (personId) => {
// a new interest
const newInterest = {
en: 'Languages', es: 'Idiomas'
};
// find the person according to personId
// and update the interests
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
return {
...person,
interests: person.interests.concat(newInterest);
};
}
return person;
});
// create a copy of the state
const newState = {
...colleagues,
persons: [...updatedPersons]
};
setColleagues(newState);
};
3) As the second example, this one feels wrong too as I am updated the entire persons array when in fact I might just want to change the age of one specific person
const onChangeValue = (personId, key, value) => {
// find the person
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
// key, could be age?
return {
...person,
[key]: value
};
}
return person;
});
// create a copy of the state
const newState = {
...colleagues,
persons: [...updatedPersons]
};
setColleagues(newState);
};
4) Is this valid, or do I need to destruct every part of my colleagues object separately?
const onChangeOtherValue = (key, value) => {
// for example changing the value foo
const newState = {
...colleagues,
[key]: value
};
setColleagues(newState);
};
I do have a feeling that only the concept of the first function is valid, while the rest of them are not.
Can this be done easily, or should I just use an immutable-helper?
Thanks in advance!
Updated examples to get syntax, right. Thanks Valerii.
To clarify
What I'm really after here is best practise to handle use cases like this one. I want to make sure my state is updated in the most correct and efficient way. So feel free to rip my examples a part or write new ones - I'm all ears. It is not necessary to simply modify mine to fit this post (unless they actually turn out to be good).
1) OK
2)
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
return {
...person,
interests: person.interests.concat({ en: 'Test', es: 'Test' })
};
}
return person;
});
const newState = {
...colleagues,
persons: updatedPersons
};
3)
const updatedPersons = colleagues.persons.map(person => {
if(person.id === personId) {
return {
...person,
[key]: value
};
}
return person;
});
// create a copy of the state
const newState = {
...colleagues,
persons: updatedPersons
};
4) OK
for the first one, i would do this way,
const newState = {
...colleagues,
persons: [...persons, newColleague]
};

Resources