REACT UPDATE A DATA - arrays

I have a problem trying to update an Array of Objects that lives in a Themecontext, my problem is with mutation, I'm using Update from Immutability helpers. the thing is that when I update my array in my specific element, This appears at the end of my object.
This is my code:
function changeValueOfReference(id, ref, newValue) {
const namevalue = ref === 'colors.primary' ? newValue : '#';
console.warn(id);
const data = editor;
const commentIndex = data.findIndex(function(c) {
return c.id === id;
});
const updatedComment = update(data[commentIndex], {styles: { value: {$set: namevalue} } })
var newData = update(data, {
$splice: [[commentIndex, 1, updatedComment]]
});
setEditor(newData);
this is my result:
NOTE: before I tried to implement the following code, but this mutates the final array and break down my test:
setEditor( prevState => (
prevState.map( propStyle => propStyle.styles.map( eachItem => eachItem.ref === ref ? {...eachItem, value: namevalue}: eachItem ))
))

Well, I finally understood the issue:
1 - commentIndex always referenced to 0
The solution that worked fine for me:
1 - Find the index for the Parent
2 - Find the index for the child
3 - Add an array []
styles : { value: {$set: namevalue} } => styles :[ { value: [{$set: namevalue}] } ]
Any other approach is Wellcome
Complete Code :
function changeValueOfReference(id, referenceName, newValue) {
const data = [...editor];
const elemIndex = data.findIndex((res) => res.id === id);
const indexItems = data
.filter((res) => res.id === id)
.map((re) => re.styles.findIndex((fil) => fil.ref === referenceName));
const updateItem = update(data[elemIndex], {
styles: {
[indexItems]: {
value: { $set: namevalue },
variableref: { $set: [''] },
},
},
});
const newData = update(data, {
$splice: [[elemIndex, 1, updateItem]],
});
setEditor(newData);
}

Related

Why the table data is not updated immediately after performing an action for the table

I have a table that has an action to delete..like this:
const deleteRow = (row) => {
let indexOfDeleted = -1;
let data = tableData;
data.forEach((item, index) => {
if (item.instrumentId === row.instrumentId) {
indexOfDeleted = index;
}
})
data.splice(indexOfDeleted, 1);
setTableData(data)
};
The data is deleted but I have to refresh it so that it is not displayed in the table.It does not seem to be rerender. What should I do?
for table:
const schema = {
columns: [
{
field: "persianCode",
title: "title",
},
],
operations: [
{
title: "delete",
icon: (
<DeleteIcon
className={clsx(classes.operationsIcon, classes.deleteIcon)}
/>
),
action: (row) => deleteRow(row),
tooltipColor: theme.palette.color.red,
}
],
};
You are mutating the state variable, in your deleteRow function. You should update the state with a copied array:
const deleteRow = (row) => {
setTableData(table => table.filter(data => data.instrumentId !== row.instrumentId))
};
Instead of finding the index and splicing it, you can just use the filter function. Since it returns a new array, we don't worry about mutating the state variable!
you will have to use Spread operator to reflect changes in react dom..
const deleteRow = (row) => {
let indexOfDeleted = -1;
let data = tableData;
data.forEach((item, index) => {
if (item.instrumentId === row.instrumentId) {
indexOfDeleted = index;
}
})
data.splice(indexOfDeleted, 1);
setTableData([...data]) /// like this
};

Merge 2 arrays into 1 array and filter this merged array,

So I have 2 arrays of objects: planned, backlog.
const planned = [
{
order_number: "1",
part_name: "example",
part_number: "1",
process_id: 1
},
....
];
const backlog = [
{
order_number: "2",
part_name: "example",
part_number: "2",
process_id: 2
},
....
];
I am trying to filter both of them at the same time, each one individually that's not a problem.
So what I am actually doing is first adding key of planned to planned array and backlog to backlog array in order to know later from where the order originally came from.
var newPlanned = planned.map(function (el) {
var o = Object.assign({}, el);
o.planned = true;
return o;
});
var newBacklog = backlog.map(function (el) {
var o = Object.assign({}, el);
o.backlog = true;
return o;
});
Later I am merging the 2 arrays into 1 array, and filtering the merged array with one input onChange, but what I can't achieve is to render the "planned" and "backlog" arrays separately from the new merged array.
const newPlannedAndBacklog = [...newBacklog, ...newPlanned];
Link to codeSandBox:
SandBox
I added a type instead of a boolean property for several types. This may be more scalable so you can add more types (refined, done, etc.), but let me know if you'd like the boolean property.
const combinedList = [
...planned.map((item) => ({ ...item, type: 'planned' })),
...backlog.map((item) => ({ ...item, type: 'backlog' })),
];
const getFilteredList = (type) => combinedList.filter((item) => item.type === type);
UPDATE
I believe this is the behavior you want. Let me know if not and I'll update the answer.
// Combines all backlog and planned stories into one array
const newPlannedAndBacklog = [
...planned.map((item) => ({ ...item, type: 'planned' })),
...backlog.map((item) => ({ ...item, type: 'backlog' })),
];
// Filters property values based on input value
const getFilteredList = (filter) => newPlannedAndBacklog
.filter(item => Object
.values(item)
.map(String)
.some(v => v.includes(filter))
);
export default function App() {
const [form, setForm] = useState({});
const [stories, setStories] = useState([]);
const handleChange = (event) => {
const { name, value } = event.target;
setForm({ [name]: value });
setStories(getFilteredList(value));
};
// Separates the return of getfilteredList based on type
const renderDiv = (type) => stories
.filter((story) => story.type === type)
.map((story) => {
// Render each property individually:
return Object.entries(story).map(([key, value]) => {
const content = <>{key}: {value}</>;
if (key === 'order_number') return <h3>{content}</h3>;
return <p>{content}</p>;
});
});
return (
<div className="App">
<h2>PlannedAndBacklog</h2>
<p>Search for planned and backlog:</p>
<input
name='type'
onChange={handleChange}
value={form.type || ''}
/>
<div>{renderDiv('backlog')}</div>
<div>{renderDiv('planned')}</div>
</div>
);
};

Problem with Hooks: useEffect, useMutation and useState working together

I have a function that is using react-table as a datagrid. It is being initially populated from Apollo in a parent component via local state with each line in the grid an object in an array.
When changes occur in a cell in the grid the whole line object is written to state.
I am trying to use useEffect to trigger a mutation that writes these changes in state back to the database, but I am struggling with two main things:
the mutation is not writing back to the database (the mutation does work in the graphql playground though)
understanding how to send only the changed row back to the mutation.
The Main Function (part of)
function Table2({ columns, data }) {
const [lines, setLines] = useState(data);
const [updateLine, {loading, error }] = useMutation(UPDATE_ITEM_MUTATION, {
variables:{ ...lines}
});
useEffect(() => {
updateLine
},[lines]);
const updateMyData = (rowIndex, columnID, value) => {
setLines(getLines =>
getLines.map((row, index) => {
if (index === rowIndex) {
console.log(row)
return {
...lines[rowIndex],
[columnID]: value
};
}
return row;
})
);
};
and the mutation...
const UPDATE_ITEM_MUTATION = gql`
mutation UPDATE_LINE_MUTATION(
$id: ID!,
$description: String,
$project:Int
$category:Int
$account:Int
$amt:Int
$units:String
$multiple:Int
$rate:Int
){
updateLine(
where:{id: $id},
data: {
description: $description
project: $project
category: $category
account: $account
amt: $amt
units: $units
multiple: $multiple
rate: $rate
}) {
id
description
amt
}
}
`
I'd be really grateful for some advice.
Thanks
I don't think you need to use useEffect, you can trigger the mutation in your update:
function Table2 ({ columns, data }) {
const [lines, setLines] = useState(data)
const [updateLine, { loading, error }] = useMutation(UPDATE_ITEM_MUTATION)
const updateMyData = (rowIndex, columnID, value) => {
const updatedLine = { ...lines[rowIndex], [columnID]: value }
updateLine({ variables: { ...updatedLine } })
setLines(getLines => getLines.map((row, index) => (index === rowIndex ? updatedLine : row)))
}
}
If you did want to use useEffect, you could e.g. keep the last changed line in a state variable and then use that to trigger the update:
function Table2 ({ columns, data }) {
const [lines, setLines] = useState(data)
const [updateLine, { loading, error }] = useMutation(UPDATE_ITEM_MUTATION)
const [updatedLine, setUpdatedLine] = useEffect(null);
useEffect(()=>{
// call your mutation
}, [updatedLine]);
const updateMyData = (rowIndex, columnID, value) => {
const updatedLine = { ...lines[rowIndex], [columnID]: value }
setUpdatedLine(updatedLine);
updateLine({ variables: { ...updatedLine } })
setLines(getLines => getLines.map((row, index) => (index === rowIndex ? updatedLine : row)))
}
}

Update item in state onClick ReactJS

So, I have class Comopnent :
state = {
tokens: [
{
name: "first",
value: 3
},
{
name: "second",
value: 2
},
{
name: "third",
value: 4
}
]
}
handleClick = (name, id) => {
const newState = this.state.tokens.map((token => {
console.log(token.name)
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div value={t.name} onClick={() => this.handleClick(t.name, t.value)}>
<img src=""/>
</div>
)
})
What i need to do - after click - to subtract 1 from value clicked token.
So - ex. after click on "First" token i want his value equal 2.
So far I've done just as much as the above.
I do not know how to go about it, i am new in ReactJS, so thanks for help in advance!
You'll have to find in your state in tokens array the object which has the same name as the argument passed in the onclick handler. Then you will have to change it's value - decrement it (value--) but you have to be aware that you can't mutate the state.
handleClick = name => () => {
const { tokens } = this.state;
const clickedToken = tokens.find(token => token.name === name);
clickedToken.value--;
const clickedTokenIndex = tokens.indexOf(clickedToken);
const newTokens = [
...tokens.slice(0, clickedTokenIndex),
clickedToken,
...tokens.slice(clickedTokenIndex + 1)
];
this.setState({ tokens: newTokens });
};
Codesandbox link: https://codesandbox.io/s/92yz34x97w
First, some things are wrong with your code.
1- You have an array of tokens, then you're mapping the list, but you don't have a key to index, this will cause weird behaviors, I improve your tokens list with keys now.
2.- You can handle the click and change the state of the tokens list, this will trigger a reload of the component.
state = {
tokens: [
{
name: "first",
value: 3,
id: 1
},
{
name: "second",
value: 2,
id: 2
},
{
name: "third",
value: 4,
id: 3
}
]
}
handleClick = (name, id) => {
const { tokens} = this.state;
const newState = tokens.map((token => {
if(token.id === id) {
token.value--;
}
return token;
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div key={t.key} value={t.name} onClick={() => this.handleClick(t.name, t.value, t.key)}>
<img src=""/>
</div>
)
})

Filter out existing objects in an array of objects

I don't think this is difficult, I just can't figure out the best way to do it. This function is creating an array, from a group of checkboxes. I then want to break up the array and create an array of objects, because each object can have corresponding data. How do I filter out existing rolesInterestedIn.roleType.
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray;
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
}
this.setState({ typeOfWork: newSelectionArray }, function() {
this.state.typeOfWork.map((type) => {
this.setState({
rolesInterestedIn: this.state.rolesInterestedIn.concat([
{
roleType: type,
}
])
}, function() {
console.log(this.state.rolesInterestedIn);
});
})
});
}
UDPATE
rolesInterestedIn: [
{
roleType: '',
experienceYears: ''
}
],
Because each time you do setState you are concatenating the new value to the prev one in rolesInterestedIn array. Add new value only when you are adding new item, otherwise remove the object from both the state variable typeOfWork and rolesInterestedIn.
Try this:
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray, rolesInterestedIn = this.state.rolesInterestedIn.slice(0);
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection);
rolesInterestedIn = rolesInterestedIn.filter(s => s.roleType !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
rolesInterestedIn = newSelectionArray.map((workType) => {
return {
roleType: workType,
experienceYears: '',
}
});
}
this.setState({
typeOfWork: newSelectionArray,
rolesInterestedIn: rolesInterestedIn
});
}
Suggestion: Don't use multiple setState within a function, do all the calculation then use setState once to update all the values in the last.

Resources