Buggy movement of Object in Array with Redux and React - reactjs

Basically I'm trying to move a single object within an Array of object, but when I move the same object once or twice it starts moving the other object in said array.
So I have tried making a new Array with .slice() then .shift(item) by it's index and then add it back in at the right index with .splice(newIndex, 0, item), once the array has been update I push it to the Redux store which updates my Megadraft(ie Draft.js) application.
I have also tried directly manipulating the original array, ie this.props.array (like your meant too with Redux) and using the keys inside of the objects instead of the indexes.
import React from 'react';
import { MegadraftPlugin, DraftJS, CommonBlock } from "megadraft"
export default class ImageGalleryBlock extends React.Component {
_moveImgOneBack = (e, images, index) =>{
e.preventDefault()
let newPlace = index - 1
if(newPlace == -1){
newPlace = images.length
}
const image = images.shift(index)
images.splice(newPlace, 0, image)
return this.props.container.updateData({ images: images })
}
_moveImgOneForward = (e, images, index) =>{
e.preventDefault()
let newPlace = index +1
if(newPlace > images.length){
newPlace = 0
}
const image = images.shift(index)
images.splice(newPlace, 0, image)
return this.props.container.updateData({ images: images })
}
render(){
return (
<CommonBlock {...this.props} actions={this.actions} title="Image
Gallery">
<BlockContent>
<div className='gallery-cms-block'>
{ this.props.images.map((obj, index)=> {
return(
<div key={obj.key} className="image-box">
<button title="Move image back one" className="move-button"
onClick={(e)=> this._moveImgOneBack(e,
this.props.data.images, index)}>◀ {index}</button>
<img className="image" src={`${obj.image.uri}?
id=${obj.image.id}`} />
<div>
<button key={obj.key} title="Move image forward one"
className="move-button" onClick={(e)=>
this._moveImgOneForward(e, this.props.data.images,
index)}>▶</button>
</div>
</div>
)
}) }
</div>
</BlockContent>
</CommonBlockMKII>
);
}
}
I expect the the button(ether forward or backward) to move said item and only the said item.
The results are that it will move the item once... maybe twice then get suck moving all the other items in the array.

... your using shift wrong:
array = ['foo', 'bar', 'not', 'feeling', 'welcome', 'by jack', 'ass users']
array.shift(whatEverIndex)
the output will always be the first index, i.e 'foo', and
because your index is correct and your using
array.splice(newIndex, 0, item)
properly your array is changing in a strange fashion.
Try copying the desired item then delete it with .splice(), like this:
const item = array[index] //copy item
array.splice(index, 1) //remove old item
array.splice(newIndex, 0, item) //place item
funny that none of you guys NaN, laruiss, Antoine Grandchamp, J-Alex took the time to actually do what you should on stackoverflow... you know help someone out. damn vete a cascarla, Good luck Reece hope this solved it for you.

Thanks #Whitepaw,
I've updated my code with:
_moveOneImgBack = (newArray, index) =>{
const arrayLength = newArray.length - 1
const newBackPlace = index == 0 ? arrayLength : index - 1
const image = newArray[index]
newArray.splice(index, 1)
// const image = images.shift(index)
newArray.splice(newBackPlace, 0, image)
this.props.container.updateData({ images: newArray })
}
and it now works perfectly, I got stuck on the fact it might have something to do with redux immutables. So thats for pointing out the misuse of .shift()

Related

How to keep my mapview.markers from rescaling when calling setGlobalState?

So i am using react native or more specifically expo mapview to render a mapview component with some custom markers. Theese markers are then connected to a scrollview in a way such that as when the scrollview is scrolled different markers corresponding to what is shown is then rescaled, aka gets 1.5x bigger this is also periodic so when im in between two they are both like 1.25x bigger code for this map with markers show below.
<MapView
ref={refMap}
style={styles.map}
region={state.region}
scrollEnabled={true}
zoomEnabled={true}
zoomTapEnabled={false}
rotateEnabled={true}
showsPointsOfInterest={false}
moveOnMarkerPress={false} // android only
minZoomLevel={13}
maxZoomLevel={16}
onMapReady={(resetMap)}
//onMarkerPress={onMarkerPress}
>
{/* creates markers on map with unique index; coordinates, img, title from mapData*/}
{state.markers.map((marker, index) => {
const scaleStyle = { // creates a scaleStyle by transforming/scaling the marker in func interpolations
transform: [
{
scale: interpolations[index].scale,
},
],
};
return (
<MapView.Marker
key={index}
coordinate={marker.coordinate}
onPress={onMarkerPress}
>
<Animated.View style={styles.markerWrap}>
<Animated.Image
source={marker.image}
style={[styles.marker, scaleStyle]} // adding the scaleStyle to the marker img (which is the marker)
//resizeMode= 'cover'
>
</Animated.Image>
</Animated.View>
</MapView.Marker>
);
})}
</MapView>
//function codes seen here
let markerAnimation = new Animated.Value(0);
//console.log(markerAnimation)
// Called when creating the markers (markers and cards has matching indexes)
// Setting inputRange (card index/position) for the animation, mapping it to an outputRange (how the marker should be scaled)
const interpolations = state.cards.map((card, index) => {
const inputRange = [
(index - 1) * Dimensions.get('window').width,
index * Dimensions.get('window').width,
((index + 1) * Dimensions.get('window').width),
];
const scale = markerAnimation.interpolate({
inputRange, //card index/positions [before, present, after]
outputRange: [1, 1.5, 1], //scales the marker [before, present, after]
extrapolate: "clamp"
});
return { scale };
});
// Called when a marker is pressed
const onMarkerPress = (mapEventData) => {
const markerIndex = mapEventData._targetInst.return.key; // returns the marker's index
let x = (markerIndex * Dimensions.get('window').width); // setting card x-position
refCards.current.scrollTo({x: x, y: 0, animated: true}); // scroll to x-position with animation
};
And whilst this whole interaction has been working exactly like i imagined i have this button that takes me to another page in the app which on arrival needs to fetch some data trough an api call. and this data depends on the index of the scrollview. In this other screen where the scrollview is also present i just use the onMomentumScrollEnd to set a global state value to the corresponding value(code for this global state shown below) this works but i would like to do exacty the same thing in the screen containing the map. but whenever a global state change is called the scaling "resets" so that the first one is the one "highlighted" even tho the scrollview is somewhere completly different.
//this is a index.js containing the store
import { createGlobalState } from 'react-hooks-global-state';
const region = {
latitude: 59.85843,
longitude: 17.63356,
latitudeDelta: 0.01,
longitudeDelta: 0.0095,
}
const { setGlobalState, useGlobalState } = createGlobalState({
currentIndex: 0,
initialMapState: {
markers: [],
region: region,
cards: [],
}
}
)
//and in a screen this can be used to change/read
import {setGlobalState, useGlobalState} from '../state' //imports it
const [currentIndex] = useGlobalState("currentIndex") //reads value
//and this is how i use the onMomentumScrollend
const onMomentumScrollEnd = ({ nativeEvent }) => {
const index = Math.round(nativeEvent.contentOffset.x / Dimensions.get('window').width);
if (index !== currentIndex) {
setGlobalState('currentIndex',index)
}
};
Any help is greatly appreciated as well im simply lost, i have tried storing the index in some sort of ref which worked fine and then try to update it on some sort of callback on the useFocusEffect but this got buggy real fast and ended up not really working. I have also tried to google to find some sort of way to keep the markers from not rerendering on state change, something which seems impossible? altough i think it would solve my problem as the markers dont depend on the state at all.
*edit
I also feel like i forgot to add that i am extremly happy to switch to any other sort of global state manager if it contains some sort of way to keep this fromh happening
**edit
So after revising this problem i found a soloution which i am not happy with, but is working.
useEffect(() => {
//scrolls back without animation when currentIndex is changed to handle react autorerender
//And scrolls if value is changed in eventscreen
refCards.current.scrollTo({
x: currentIndex * Dimensions.get("window").width,
y: 0,
animated: false,
});
}, [currentIndex]);

React useState not updating mapped content

I feel like im missing something that is staring me right in the face. I am trying to have content stored in an array of objects update when a checkbox is on or off. The console log is showing the object data is updating correctly so I assume my fault resides in not understanding useState fully?
const [statistics, setStatistics] = useState([
{
id: 1,
content: <div>Content1</div>,
state: true,
},
{
id: 2,
content: <div>Content2</div>,
state: true,
},
]);
In the component:
{statistics.map((item) => (item.state ? item.content : <></>))}
<input
type="checkbox"
onChange={(e) => {
let newArr = statistics;
e.target.checked
? (newArr[0].state = true)
: (newArr[0].state = false);
setStatistics(newArr);
console.log(statistics);
}}
/>
You are trying to change the state directly, instead you need to work with a copy of the state and make all changes to it.
Just replace in your code this string:
let newArr = statistics; // as link to base array
to
let newArr = [...statistics]; // as new copy of base array
and it will works.
React skips all state changes if they are made directly.
To create a new array as copy/clone of another array, in ES6, we can use the spread operator. You can not use = here, since it will only copy the reference to the original array and not create a new variable. Just read here for reference.
In your case, your newArray will refer to the old statistics and will not be detected as the new state. That is why no re-render takes place after you made changes to it.
So here, you can do this:
return (
<>
{statistics.map((item) => (item.state ? item.content : <></>))}
<input
type="checkbox"
onChange={(e) => {
setStatistics((prevStats) => {
const newStats = [...prevStats];
e.target.checked
? (newStats[0].state = true)
: (newStats[0].state = false);
return newStats;
});
}}
/>
</>
);

Filtering fetched array in render React

New to React here so please bear with me. I fetched a list of data from a server and that I put in the State though the componentDidMount function. In it, I push the data to my State array named solo. It works great until I try to render the array. I need to filter my array to map the data based on one property (categ) matching a property from another array, and create buttons in the right category. When I run my code, I only get one button rendered, whichever is first in my solo array, in which all my data appears. I'm sure it has someting to do with the asynchronous nature of fetch, but I don't understand what.
Here's my code :
componentDidMount(){
// Get URLS from config
const data = this.props.config.layerUrls
// Init state values
let categ = [...this.state.categ]
let solo = [...this.state.solo]
// Loop through categories
for (let i = 0; i < data.length; i++) {
let donneesCateg = data[i]
let listURLS = donneesCateg["urls"]
categ.push({['categ']: donneesCateg["name"], ['type']: "categ", ['urls']: listURLS })
this.setState({ categ : categ })
// Loop through individual URL data
for (let a = 0; a < listURLS.length; a++) {
fetch(listURLS[a] + "?f=pjson")
.then(res => res.json())
.then(data => {
// Push to state array
solo.push({['categ']: donneesCateg["name"], ["type"]: "solo", ["couche"]: data.name, ["url"]: listURLS[a] })
this.setState({ solo : solo })
})
}
}
}
render() {
{
return (
<p className="shadow-lg m-3 p-1 bg-white rounded">
{this.state.categ.length!=0 ? this.state.categ.map(item => {
return (
<div>
<button id={item.categ} className="categ" value={item.urls} onClick={this.selectChangeMultiple}>{item.categ}</button>
{this.state.solo.length!=0 ? this.state.solo.filter(solo => solo.categ == item.categ).map(data =>
<button id={data.categ} className="btn" value={data.url} onClick={this.selectChangeSingle}>{data.couche}</button>
) : <p id="loadingMsg">Solo data Loading...</p>}
</div>
)
}) : <p id="loadingMsg">Categ data Loading...</p>}
</p>
</div>
);
}
}
Note : I have to go through this loop system because the URLs I use to fetch the data are in a JSON in which they are stored by categories.
Many thanks.
In your code, any subsequent call to setState({solo: solo}) isn't recognized as an update by React. This happens because solo is always the same "object" (although mutated with push, but React comparison algorithm doesn't go so far as to compare the new and previous. It just compares the old and new solos with something like ===.
An obvious fix would be to call this.setState({ solo : [...solo] }) instead of this.setState({ solo : solo }), although it would still may cause too many extra rerenders. But it'd be a good start.

React.useState Hook only displaying length of object, not object itself

Basically, I have a component named Upload:
const Upload = () => {
const [scene , setScene] = React.useState([]);
// handleUpload = (event) => {
// console.log('Success!');
// }
function renderHandler(s){
console.log('[RENDER] calling...')
console.log(s);
if(scene.length == 0){
console.log('[RENDER] Denied!')
return(
<div>
Nothing is rendered..
</div>
)
} else {
console.log('[RENDER] Working...')
console.log('[RENDER] Received planes:');
console.log(blockState);
console.log(scene);
return (
<View top={blockState}/>
)
}
}
function parseNBT(input) {
setScene(scene.push('1'));
setScene(scene.push('2'));
console.log('scene:');
console.log(typeof scene);
console.log(scene);
console.log('\n+blockState:');
console.log(typeof blockState);
console.log(blockState)
}
return (
<Container>
Input NBT file <br/>
<input type="file" onChange={handleChange}></input>
{renderHandler(scene)}
</Container>
)
}
The issue here is, when I'm setting the scene's state in parseNBT, and console log scene, it gives me the array:
However, when I call it from renderHandler, it simply returns the length of the array, in this case it was 2
Very weird, maybe i'm missing something?
The .push returns the length of the array.
Return value
The new length property of the object upon which the method was called.
Try
setScene( currentScene => [...currentScene, '1'] );
setScene( currentScene => [...currentScene, '2'] );
To summerize briefly, you are treating 'scene' as a mutable object, when it is immutable. Meaning, when you are trying to do a 'scene.push' it is trying to modify an immutable object. A regular array is mutable, but not a react state array. Therefore, you do not want to give an update to scene directly, you want to take its previous state, concatenate it with your new desired value, then make that new value your new state.
Like so:
Replace your lines:
setScene(scene.push('1'));
setScene(scene.push('2'));
with:
setScene((scene) => [...scene, 1]);
setScene((scene) => [...scene, 2]);

Reactjs, how to instanciate object from array, and update render

I'm struggling with reactjs for no reason. I'm a little confused about the magic behind and I'm not able to perform a simple operation of adding object / removing object from an array and display it.
I my parent, I have a method which on click append a new element:
appendNewPma(){
var newPma = this.state.pma.slice();
newPma.push(PmaType1);
this.setState({pma:newPma})
}
then my render method is like that:
render() {
return (
<div>
<a className="waves-effect waves-light btn" onClick={this.appendNewPma}>new</a>
{this.state.pma.map((Item, index) => (
<Item
key = {index}
ref = {"pma" + index.toString()}
onDelete = {() => this.onDelete(index)}
title = {index}/>
))}
</div>
);
}
Append work fine, but my array doesn't contain an object to display but rather a magical function that I don't understand.
But when I try to delete an object:
onDelete(idx){
console.log(idx);
var pma = this.state.pma.slice();
pma.splice(idx, 1);
this.setState({pma:pma})
}
When I delete from the array, no matter what index I will remove, it will only remove the last object. I know my code is not ok, but I have no idea how you can render element for an array of object (here my array is list of function constructor).
It will work better if I could get a straight ref to my object. Of course, I tryed to removed from the ReactDom, but was complening I was not updating from the parent...
I just want a simple array push/pop pattern with update.
Thanks for your help
Try below code. hope so it solve your issue.
addToArray = (event) => {
this.state.pma.push({"name ": "xyz"});
this.setState(
this.state
)
}
removeFromArray =(index) => {
var updatedArr = this.state.pma.splice(index, 1);
this.setState({arr : updatedArr})
}

Resources