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

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

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

React - problem updating an array of objects using hooks

I have tried a bunch of approaches but on this, but it's still bugging the hell out of me. I am using React 17.0.1 in this project.
I have a array of objects formatted like so:
gameNumberFields: [{ name: 'gameNum', placeholder: 'GGG', size: '3', value: '', dataindex: '0' }];
For now, there is just one object in the array, but there is always the possibility of more down the road (hence why it's an array).
In the code - this field is pre-populated on initialization - so the "value" of the first index in the array might be something like "123". I use initialState to make this happen:
const [gameNumberFields, setGameNumberFields] = useState(scratchTicketFields?.gameNumberFields ?? []);
When the display is shown to the user - this value is shown to the user in an field using the defaultValue.
return gameNumberFields.map((field, index) => {
const ref = React.createRef();
elemRefs.push(ref);
return (
<div className='d-inline-block ticket-number-inputs' key={`game-numbers--${index}`}>
<input
type='text'
id={field.name}
data-index={field.dataindex}
ref={ref}
className='theme-input'
placeholder={field.placeholder}
size={field.size}
maxLength={field.size}
defaultValue={field.value}
onBlur={(event) => handleGameNumberChange(event, field)}
onKeyUp={(event) => autoTab(event, elemRefs)}
required
/>
<span className='dash'>—</span>
</div>
);
});
}; // end GameIdFieldInputs
So far - so good. The problem I am having is in the onBlur event handler. For some reason - when the user changes the value to something else - it always goes back to the old value.
Here is the handler:
const handleGameNumberChange = async (event, field) => {
// get gameNum from the event target
const gameNum = event.target.value; // say this becomes 999
// do a deep copy of the gameNumberField state.
let gameIdField = JSON.parse(JSON.stringify(gameNumberFields));
// check that we are changing the right index in the array
const fieldIndex = gameIdField.findIndex((obj) => obj.name == field.name);
// make a new object changing the value to 999
let newGameObject = { ...gameIdField[fieldIndex], value: gameNum };
console.log('newGameObject', newGameObject);
//NOTE: At this point the newGameObject is correct and updated with the NEW gameNum of 999
// create a new array and PUSH the new game object onto it
let newGameIdArray = new Array();
newGameIdArray.push(newGameObject);
// Once pushed the array has the OLD game number in it . . . so 123 - WHY?!?!
console.log('newGameObjectArray', newGameIdArray);
setGameNumberFields(newGameIdArray); // updates with the 123 game number. . .
}; // end handleGameNumberChange
So in the method, I deep copy the gameNumberFields into a mutable object. I then update the object with the new gameNumber (from 123 to 999) and all works when I print it out with my console.log for newGameObject.
As soon as I push this object in the new Array - it changes back to 123! Can anyone see a flaw in my code here?
When I finally do call setGameNumberFields - it does set the state (I have a useEffect that prints out the values) but again, its always the OLD values.
Any help is welcome and appreciated!

How to resolve "serializes to the same string" message with Jest?

In my React app, I've built a function that accepts a string full of regular text and any number of URLs. It then converts these into a <span> in React with every URL inside of an <a href tag. The code works really well but I can't seem to write a Jest test for it.
Here's what I've tried so far:
expect(convertHyperlinks('http://stackoverflow.com'))
.toStrictEqual(<span><a href='http://stackoverflow.com' target='_blank'>stackoverflow.com</a></span>);
And:
expect(convertHyperlinks('http://stackoverflow.com'))
.toMatchInlineSnapshot(<span><a href='http://stackoverflow.com' target='_blank'>stackoverflow.com</a></span>);
In the former case I'm getting the "serializes to the same string" message.
In the latter case, it's showing me this:
Expected properties: <span>stackoverflow.com</span>
Received value: <span>stackoverflow.com</span>
Might anyone know how to build a passing test for this?
Robert
Update: Here's the code for the function in question:
export const convertHyperlinks = (text: string): React.Node => {
// Find all http instances
const regex = /http\S*/g;
const hyperlinkInstances = text.match(regex);
if (!hyperlinkInstances) {
return <span>{text}</span>;
}
// Break up `text` into its logical chunks of strings and hyperlinks
let items = [];
let idx1 = 0;
let idx2 = -1;
hyperlinkInstances.forEach((hyperlink) => {
idx2 = text.indexOf(hyperlink, idx1);
if (idx2 === idx1) {
items.push(hyperlink);
idx1 += hyperlink.length;
} else {
items.push(text.substring(idx1, idx2));
items.push(hyperlink);
idx1 = idx2 + hyperlink.length;
}
});
if (idx1 < text.length) {
items.push(text.substring(idx1, text.length));
}
return (
<span>
{items.map((item) => {
if (item.includes('http://')) {
const plainLink = item.replace('http://', '');
return (
<a href={item.toLowerCase()} target='_blank' key={plainLink}>
{plainLink}
</a>
);
} else {
return item;
}
})}
</span>
);
};
You are returning a ReactNode from the method, which is an object. But you are trying to assert as just a string. It would'nt work.
This is what you may be getting back from the method,
And so, you must assert against the object you got, and not the way you are doing it right now,
const result = convertHyperlinks('http://stackoverflow.com')
expect(result.props[0].key).equals('stackoverflow.com');
// similar kind of assertions.
Additionally, I would suggest you go the component route and just render the component in the test method and assert for presence of elements as opposed to diving into react objects.
A representation of the same is as follows,
Here is your component,
const ConvertToHyperlinks = ({text}: {text: string}) => {
// your logic and then returning DOM elements.
return <></>;
}
Then you use it anywhere as,
<div>
<ConvertToHyperlinks text={'https://www.test.com/'} />
</div>
In your unit test you can then,
const renderedComponent = render(<ConvertToHyperlinks text={''https://www.anytyhing.com}/>);
expect(renderdComponent.getByText('anytyhing.com')).ToBeInTheDocument();
Here I am using some Rect Testing Library method but the idea is same even if you use enzyme etc.

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