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

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})
}

Related

.filter() function creating loop in delete function - React

I've got a 'list' component, which allows you to add an item to a state array, and then display it from the state afterwards. These list items can then be removed by the user afterwards (or should be able to).
There's four state props in this component:
currentList: content in the input that's to be added to the list array
setCurrentList: what's used to change the content in the input
fullList: the full array list
setFullList: used to add the currentList content to the array, and removed
I'm using .filter() to create a copy of the state array, and then set the state afterwards in this function:
const deleteFromList = (e) => {
console.log("Delete button pressed")
console.log(e)
let fullList = props.fullListState
let setFullList = props.setFullListState
let filteredArray = fullList.filter(item => item)
setFullList(filteredArray)
}
However, every time I execute this function (i.e. when the delete button is pressed), it just creates a loop and the first two console.logs are just repeatedly done.
This is the full return function itself:
<>
<label className="setup-jobs-label">{props.label}</label>
<div className="setup-jobs-input-container">
<input className="setup-jobs-alt-input" type="text" onChange={onChange} value={props.currentListState} />
<button className="setup-jobs-add-button" onClick={addToList}>Add</button>
</div>
{ props.fullListState === [] ? null : props.fullListState.map(x => {
return <div className="setup-jobs-input-container" key={props.fullListState[x]}>
<p className="setup-jobs-input-paragraph">{x}</p>
<button className="setup-jobs-delete-button" onClick={deleteFromList(x)}>Delete</button>
</div>
}) }
</>
The important bit is the bottom conditional render, which checks to see if the state array is empty, and if so, not display anything. If it isn't, then it returns null.
Any advice would be appreciated - not sure what I'm doing wrong in the filter function.
In your onClick handler, you pass the result of the execution of deleteFromList, you should pass a reference to this function instead :
// note the '() =>'
<button className="setup-jobs-delete-button" onClick={() => deleteFromList(x)}>Delete</button>
See https://reactjs.org/docs/handling-events.html for more details about this.
Beside this, your filter logic does not seem right :
// this line only removes falsy values, but not the "e" values
let filteredArray = fullList.filter(item => item)
// you should implement something like this
let filteredArray = fullList.filter(item => [item is not "e"])
// this should work as we work on objects references
let filteredArray = fullList.filter(item => item !== e)

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]);

Flickity cell selector in React

Basically I have a dropdown in which each option has an attribute corresponding to the ID of an image. My goal is to go to that image when an option is chosen. For that I am trying to use:
const myCustomNext = () => {
flkty.selectCell(somevar)
};
somevar is initially set to #someid, when I click my button, it goes to the cell with that ID perfectly.
The issue starts once I update the value of somevar. As soon as I do and then click the button, I get the following Error:
"Cannot read property 'selectCell' of null"
I logged both the initital somevar and the updated one. Other than the ID itself, they are absolutely identical so I have no clue where I am going wrong. I tried switchen the static and reloadOnUpdate settings but that didn't help.
Here a more complete example that might show better what I am trying to do:
const FlickTest = () => {
const [varimg, setVarimg] = useState("#cG9zdDoxNA");
let flkty = null;
function setVariant(e) {
let index = e.target.selectedIndex;
let optionElement = e.target.childNodes[index]
let option = optionElement.getAttribute('data-imgid');
setVarimg(`#${option}`);
}
const myCustomNext = () => {
flkty.selectCell(varimg)
};
return (
<>
<button onClick={myCustomNext}>My custom next button</button>
<select onChange={setVariant}>
{variants.map((variant) =>
<option data-imgid={variant.gallerie[0].id} value={variant.farbe} key={variant.farbe}>{variant.farbe}</option>
)}
</select>
<Flickity
flickityRef={c => (flkty = c)}
className={'carousel'}
elementType={'div'}
options={flickityOptions}
disableImagesLoaded={true}
reloadOnUpdate={true}
static={true}
>
{variants.map((galitem) =>
galitem.gallerie.map((galimg) =>
<div key={galimg.id} id={galimg.id.replace(/[^\w\s]/gi, '')}>
<span>{galimg.id}</span>
<Image fluid={galimg.localFile.childImageSharp.fluid} />
</div>
)
)}
</Flickity>
</>
)
}
Any ideas or pointers would be much appreciated :)
Switched from a dropdown to buttons just to simplify the whole thing and see where it goes wrong. Seems like flickity only accepts the value directly but not from state or any other variable.
This works:
const selectSlide = (e) => {
flkty.selectCell( `.${e.target.getAttribute("data-selector")}` )
};
...
<button onClick={selectSlide} data-selector={variant.gallerie[0].id} key={variant.farbe}>{variant.farbe}</button>
If anybody knows if this is a flickity thing or (more likely) I was doing something completely wrong I'd still appreciate some pointers so I know better next time :)

Buggy movement of Object in Array with Redux and React

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()

React/JSX - How to render a list alphabetically

I am working on someone else's code and trying to figure out how to render a list alphabetically in React. I didn't write any of this and have very little knowledge of React, so please bear with me.
The ul looks like this:
<ul className="prod-group__filter__options" ref={(options) => this.options = options}>
{
filter.dropdown.map((option, index) => (
this.renderOption(filter, option, index)
))
}
</ul>
and the renderOption function, which obviously renders the list items looks like this:
renderOption(filter, option, index) {
return (
<li className="prod-group__filter__option" key={index}>
<label className="prod-group__filter__option__label">
<input name={filter.name}
type="checkbox"
defaultValue={option.slug}
checked={option.checked}
onChange={this.optionChangeHandler} />
{option.label}
</label>
</li>
);
}
The value I am trying to alphabetize is option.slug which is coming from a json list. Can anyone help me get a bit closer to rendering this list alphabetically?
It looks like filter.dropdown is your array of options. This array is passed to the .map() method which then runs a renderOption method in the given order.
Hence, you should sort the filter.dropdown array in your ul component code just before calling .map() on it.
You will have to sort option alphabetically using plain javascript before calling filter.dropdown.map... on it. I would advice using lodash function _.sortBy(option, 'slug'); where option is an array of objects with a property called slug then you can pass the sorted result to your map function.
In case anyone is interested, the solution was to sort the list items before calling .map() on it as both macbem and finch suggested. I used const to create an "items" constructor that I could then pass into the ul further down:
const items = [].concat(this.props.options)
.sort((a, b) => {
const One = a.slug.toUpperCase();
const Two = b.slug.toUpperCase();
return (One < Two) ? -1 : (One > Two) ? 1 : 0;
})
.map((option, index) => this.renderOption(name, option, index));
return (
<div className={classes}>
<a className="prod-group__filter__dropdown"
ref={(trigger) => this.trigger = trigger}
onClick={this.triggerClickHandler}>
{label}
</a>
<ul className="prod-group__filter__options" ref={options => this.options = options}>
{ items }
</ul>
</div>
);

Resources