I'm trying to rearrange div's with React/Redux via Drag&Drop and there is a strange behavoir that i cant explain. I have the following reducer (reduced version for readability)
There are 5 "console.log" around the middle of the code. When i log state or structs the chrome console will print the already rearrange version. why?
export default function reducer(
state={
structures: [],
elementUniqueCounter: 0,
hoveredElement: null,
DnD: {
dragContent: null,
dragOverContent: null,
dragContentTop: true,
dragStructure: null,
dragOverStructure: null,
dragStructureTop: true,
content: null,
mousepositon: {}
}
}, action) {
let DnD = Object.assign({}, state.DnD);
let structs = state.structures.slice();
switch (action.type) {
case "STOP_DRAG_CONTENT":
let cindex_source;
let index_source;
let cindex_target;
let index_target;
let foundTarget = false;
let foundSource = false;
structs.map(function (struct, index) {
struct.content.map(function (content, cindex) {
if(content.id === DnD.dragOverContent.props.id) {
cindex_target = cindex;
index_target = index;
foundTarget = true;
}
if(content.id === DnD.dragContent.props.id) {
cindex_source = cindex;
index_source = index;
foundSource = true;
}
});
});
console.log(state);
console.log(index_source);
console.log(cindex_source);
console.log(index_target);
console.log(cindex_target);
if(index_source !== undefined && cindex_source !== undefined && index_target !== undefined && cindex_target !== undefined) {
let copy = structs[index_source].content.slice(cindex_source, cindex_source+1)[0];
copy.content = DnD.content;
structs[index_source].content.splice(cindex_source, 1);
if (DnD.dragContentTop) {
structs[index_target].content.splice(cindex_target+1, 0, copy);
} else {
structs[index_target].content.splice(cindex_target, 0, copy);
}
}
DnD = {
dragContent: null,
dragOverContent: null,
dragContentTop: true,
dragStructure: null,
dragOverStructure: null,
dragStructureTop: true,
content: null,
mousepositon: {}
};
return {...state, DnD: DnD, structures: structs};
}
return state
}
It's not that it is printing the rearranged version before it happens. It is that it is printing an object that you are mutating. By the time you look at the object in the console the mutation has already occurred.
The use of splice is mutating.
structs[index_source].content.splice(cindex_source, 1);
if (DnD.dragContentTop) {
structs[index_target].content.splice(cindex_target+1, 0, copy);
} else {
structs[index_target].content.splice(cindex_target, 0, copy);
}
EDIT:
The above mutation is actually mutating the nested properties of state.structures. This is happening because .slice() returns a shallow copy of the original object.
The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
The objects in the shallow copy are just pointers to the objects in state.structures. So when you use .splice(), you mutate those referenced values.
Here is a snippet to illustrate this. Run it an look at the console.log.
const people = [
{
name: "Joe",
age: "52"
},
{
name: "Sally",
age: "48"
}
];
const clonedPeople = people.slice();
console.log("people === clonedPeople: ", people === clonedPeople);
console.log("people[0] === clonedPeople[0]: ", people[0] === clonedPeople[0]);
const deeplyClonedPeople = JSON.parse(JSON.stringify(people));
console.log("people[0] === deeplyClonedPeople[0]: ", people[0] === deeplyClonedPeople[0]);
Hope this helps!
Related
const [dammy, setDammy] = useState({
name: "any",
status: {
"1.0.1": "Successfylly installed",
"1.0.2": "Successfylly installed",
"1.0.3": "Successfylly installed",
"1.0.4": "Successfylly installed",
"1.0.6": "Installing",
"1.0.7": "In Queue",
"1.0.8": "In Queue",
"1.1.1": null,
"1.1.2": null,
"1.1.36": null,
"1.1.4": null,
"1.1.5": null,
"1.1.6": null,
},
available: ["1.1.1", "1.1.2", "1.1.36", "1.1.4", "1.1.5", "1.1.6"],
queue: ["1.0.7", "1.0.8"],
date: new Date()
})
function addingToQueue(e: MouseEvent < HTMLDivElement > , i: number) {
setDammy(prev => {
let val = prev["available"].splice(i, 1)
prev["queue"].push(val[0])
console.log(prev) // <-- I could see changes has been applied
return prev
})
}
component doesn't rerender even tho I could see that in setDummy console show's the right object data. But after completing function pre-render doesn't happen and on a screen I see no changes.
Because you're not actually returning a new array in your callback to setDammy-- you're merely mutating the previous one and returning it; when React does its comparison, it will see that the returned object is still the same reference. Instead, spread into a new array and alter that:
setDammy(prev => {
const newArr = [...prev];
let val = newArr["available"].splice(i, 1)
newArr["queue"].push(val[0])
console.log(newArr) // <-- I could see changes has been applied
return newArr
})
I'm currently completing a project where I have to build a Spotify playlist creator. For part of this, I need to add or remove tracks from the playlist. I've coded a method that appears to work, but it is different from the official solution, so I just want to see whether there is a reason I shouldn't do it my way.
Specifically, they use .find and .filter methods where I have used .includes. Is there a downside to what I've done?
Their code
addTrack(track) {
let tracks = this.state.playlistTracks;
if (tracks.find(savedTrack => savedTrack.id === track.id)) {
return;
}
tracks.push(track);
this.setState({ playlistTracks: tracks});
}
removeTrack(track) {
let tracks = this.state.playlistTracks;
tracks = tracks.filter(currentTrack => currentTrack.id !== track.id);
this.setState({playlistTracks: tracks});
}
My code
addTrack(track) {
let tracks = this.state.playlistTracks;
if (!tracks.includes(track)) {
tracks.push(track);
}
this.setState({playlistTracks: tracks});
}
removeTrack(track) {
let tracks = this.state.playlistTracks;
if (tracks.includes(track)) {
let index = tracks.indexOf(track);
tracks.splice(index, 1);
}
this.setState({playlistTracks: tracks});
}
Yes, there is a significant difference, because includes() will only return true if you pass it the actual instance (by that I mean a reference that points to the same object) of track that you are looking for.
The provided solution compares tracks only based on the track ID, so that is something different.
See the following example:
const tracks = [
{id: 1, title: "mysong1"},
{id: 2, title: "mysong2"},
]
function isTrackPresentIncludes(track){
return tracks.includes(track);
}
function isTrackPresentFind(track){
return tracks.find(it => it.id === track.id) !== undefined;
}
// this will be true
console.log("with includes(tracks[0]):\n", isTrackPresentIncludes(tracks[0]))
// this will be false as it's a different object
console.log("with includes({id: 1, title: \"mysong1\"}):\n", isTrackPresentIncludes({id: 1, title: "mysong1"}))
// this will be true
console.log("with find(tracks[0]):\n", isTrackPresentFind(tracks[0]))
// this will also be true
console.log("with find({id: 1, title: \"mysong1\"}):\n", isTrackPresentFind({id: 1, title: "mysong1"}))
You have the same issue with indexOf() in your removeTrack().
There is another thing I don't particularly like about the solution. find() returns the track that was found but that return value is never actually used so to my mind you should use some() instead which just returns true or false.
I don't think this is a problem here but it could potentially lead to unexpected behavior if an array would hold falsy values.
Consider this:
const arrayWithFalsyValues = [
0, // zero is falsy!
1,
2,
3,
4,
5,
6,
7,
8
]
function isPresent(toBeFound){
if(arrayWithFalsyValues.find(number => number === toBeFound)){
console.log(`Value ${toBeFound} found in array`);
}
else{
console.log(`Value ${toBeFound} NOT found in array`);
}
}
console.log("Array:", arrayWithFalsyValues)
// this will work as expected
console.log("Contains 3?")
isPresent(3)
console.log("Contains 8?")
isPresent(8)
console.log("Contains 10?")
isPresent(10)
// now search for the falsy value -> incorrect result
console.log("Contains 0?")
isPresent(0)
Issue is with referencing, You have make another reference of playlistTracks
addTrack(track) {
let { playlistTracks } = this.state;
let tracks = [...playlistTracks];
if (!tracks.includes(track)) {
tracks.push(track);
}
this.setState({ playlistTracks: tracks });
}
removeTrack(track) {
let { playlistTracks } = this.state;
let tracks = [...playlistTracks];
if (tracks.includes(track)) {
let index = tracks.indexOf(track);
tracks.splice(index, 1);
}
this.setState({ playlistTracks: tracks });
}
MY SUGGESTION
addTrack(track) {
const { playlistTracks } = this.state;
const tracks = [...playlistTracks];
const index = tracks.indexOf(track);
if (index < 0) {
tracks.push(track);
}
this.setState({ playlistTracks: tracks });
}
removeTrack(track) {
const { playlistTracks } = this.state;
const tracks = [...playlistTracks];
const index = tracks.indexOf(track);
if (index > -1) {
tracks.splice(index, 1);
}
this.setState({ playlistTracks: tracks });
}
When i remove images from news i catch id, and id come to along.
How to write in array all this lonlies id ?
How to create streamIds array with streamId ?
this.state = {
mainImage: null,
mainImageUrl: "",
crop: {
aspect: 2 / 1
},
pixelCrop: null,
cropped: false,
loaded: false,
streamIds: []
};
removeImage(imageKey, streamId) {
const {singleNews} = this.props;
let streamIds = this.state.streamIds;
console.log(streamId);
singleNews.secondaryImages.splice(imageKey, 1);
if (!singleNews.secondaryImages.length) {
singleNews.secondaryImages = null;
delete singleNews.secondaryImages;
this.props.updateSingleNews(null, singleNews);
} else {
streamIds.push(streamId);
singleNews.secondaryImages.map(image => {
const index = singleNews.secondaryImages.indexOf(image);
if (index > -1) {
singleNews.secondaryImages.slice(index, 1);
FilesApi.getDocument(image.streamId).then(resp => {
singleNews.secondaryImages[index] = new File([resp], image.name, {lastModified: Date.now()});
});
}
});
this.props.updateSingleNews('streamIds', streamIds);
}
}
this is your method
If not in this func where i need to place
if you want to keep the array of ids in the same component, use
let streamIds = [];
at the top of your react component and do
removeImage (imageKey, streamId) {
console.log(streamId);
streamIds.push(streamId); // insert the item to array
}
in your removeImage method
if you want to keep the removed ids in the application state, then the concept is the same, but it need to be done on the state management tool you are using (like redux, mobx etc)
I have an array which is a state of the React component. This array is a checklist.
var units1 = this.state.units;
when I update units1, this.state.units changes without the this.setState({ units: units1 })
I use this.setState({ a: 2 }); just to see if the array was updated without this.setState({ units: units2 });
this.state.units gets its value from props so if the state changes the props also changes.
handleItemChange(e) {
var units1 = this.state.units.slice();
var unit_name = parseInt(e.target.attributes.getNamedItem('data-unit_name').value);
var new_unit;
if (!e.target.checked && this.state.units && this.state.units.length > 0) {
this.state.units.map((unit) => {
if (unit_name == unit.codpacunidad) {
if (unit.topics && unit.topics.length > 0) {
unit.topics.map((topic) => {
if (topic.codpacunidadtema == e.target.name) {
new_unit = unit;
var index = units1.indexOf(unit);
//units1.splice(index, 1);
units1 = update(units1, {$splice: [[index, 1]]})
var topics1 = unit.topics.slice();
index = topics1.indexOf(topic);
//topics1.splice(index, 1);
topics1 = update(topics1, {$splice: [[index, 1]]})
new_unit.topics = topics1;
}
});
}
}
});
} else {
var found_unit = false;
var name = parseInt(e.target.name);
var order = parseInt(e.target.attributes.getNamedItem('data-order').value);
var unit_order = parseInt(e.target.attributes.getNamedItem('data-unit_order').value);
if (this.state.units && this.state.units.length > 0) {
this.state.units.map((unit) => {
if (unit.codpacunidad == unit_name) {
found_unit = true;
new_unit = unit;
var index = units1.indexOf(unit);
units1.splice(index, 1);
var new_topic = {
codpacunidadtema: name,
orden: order
};
var topics2 = new_unit.topics;
new_unit.topics = update(topics2, { $push: [new_topic]});
}
});
}
if (found_unit == false) {
new_unit = {
codpacunidad: unit_name,
orden: unit_order,
topics: [{codpacunidadtema: name, orden: order }]
};
}
}
// var units2 = update(units1, { $push: [new_unit]});
// this.setState({ units: units2.sort(function(a, b) {
// return parseInt(a.orden) - parseInt(b.orden);
// })
// });
this.setState({ a: 2 }); //just to test if the array gets updated without this.setStaet({ units: units2 })
}
Anybody knows why this is happening?
As #Alexander van Oostenrijk said to make deep copy of array.
Because array are passed by reference which mean memory address of the array is passed not the value of array.
var units1 = this.state.units.slice();
Now units1 has the reference address of that array any change made to units1 or this.state.units.slice() will change value of both.As basically they are using address and if one change the value at address then both will read changed value.Hope you understand
To create deep copy you can create new object like
var units1 = Object.assign([],this.state.units)
This will create new object with data of this.state.units
extras I think you do not need .slice().
i am having a problem where i cannot able to increment the ID of the state element for every addition of elements in the row. What i am getting is same number is repeating for every addition of elements, i need something like ID should be 1 to n numbers of addition.
In text-box, i am not entering the ID, i enter only the LText(Name).
In general, the ID should generate for every addition of elements.
What i have tried is..
export default class EPIM091 extends cntrl.WITBase {
constructor(props) {
super(props);
const witState = this.state;
if (Object.keys(witState).length == 0) {
witState.model = { LID: 1, LText: '', SOrder: '', Inventoried: false, Location:[] }; //Here i need to store all other state values to the Location Array
}
}
clickAction = (e) => {
if (e.id == 'btn_add') {
//var i = 1;
const { model } = this.state;
if (model.LText != null) {
if ((model.Location || []).length == 0) {
model.Location = [];
}
// this.setState(prevState => { return { LID: e.id == 'btn_add' ? prevState.LID + 1 : prevState.LID - 1 } }); //This does not worked
model.Location.push({ "LID": model.LID.toString(), "LText": model.LText, "SOrder": model.SOrder, "Inventoried": model.Inventoried.toString() });
this.setState({
model: model,
LID: model.LID + 1 //This also not worked
});
}
}
};
render(){
// Due to confusion of code, i did not add the textboxes codes
<cntrl.WITButton id="btn_add" onWitClick={this.clickAction} />
}
I need something like when i add, i should get the Unique LID from 1 to the number of elements added. What i am getting is same ID i.e 1. Thank you
Many problems here :
1) You are trying to modify a variable that you declared constant:
const witState = this.state;
if (Object.keys(witState).length == 0) {
witState.model = { LID: 1, LText: '', SOrder: '', Inventoried: false, Location:[] };
2) You are trying to access state variable, but you never define it:
const { model } = this.state;
3) You try to modify it even if it's declared constant:
if (model.LText != null) {
if ((model.Location || []).length == 0) {
model.Location = [];
}
// this.setState(prevState => { return { LID: e.id == 'btn_add' ? prevState.LID + 1 : prevState.LID - 1 } }); //This does not worked
model.Location.push({ "LID": model.LID.toString(), "LText": model.LText, "SOrder": model.SOrder, "Inventoried": model.Inventoried.toString() });
this.setState({
model: model,
LID: model.LID + 1 //This also not worked
});
}
Unless there is a lot of code you didn't show us, you must have a lot of errors in your console. Watch it.
-[Edit]-:
The problem is that I can't really help you unless I have the whole code, because as it is, it doesn't make sense. If you just want to increment your LID state var, here is a working example:
constructor(props) {
super(props);
this.state={LID:1}
}
test=(e)=>{
let {LID} = this.state;
this.setState({LID:LID+1})
console.log(this.state.LID);
}
render(){
return(
<button onClick={this.test}> Test</button>
)
}