Why my setState doesn't accept this object? - javascript

I'm working in a to-do list on React and when method deleteItemis called, I get Uncaught Error: Objects are not valid as a React child (found: object with keys {text}) but I don't understand the reason
class Form extends Component{
constructor(props){
super(props);
this.state = {
task: "",
list: []
}
this.saveItem = this.saveItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
saveItem(event){
let state = this.state;
event.preventDefault();
if(this._taskInput.value !== ""){
state.list.push({ text: this._taskInput.value });
this.setState(state);
}
this.setState({task: ""});
}
deleteItem(index){
const {list} = this.state;
this.setState({
list: list.filter((item,itemCurrent) => {
// console.log(item.key, index);
return (itemCurrent !== index);
}),
});
}
render(){
return(
<Fragment>
<form onSubmit={this.saveItem}>
<input value= {this.state.task} id="to-do"
onChange={(e) => this.setState({task: e.target.value})}
ref={event => this._taskInput = event} type="text" className="validate"/>
<label htmlFor="to-do">My tasks</label>
</div>
<button type="submit" name="action">Submit
</button>
</div>
{{this.state.task}}
{this.state.list}
</form>
</div>
<Table list= {this.state.list} deleteItem = {this.deleteItem}/>

first of all there's a big conceptual error here:
if (this._tarefaInput.value !== ""){
state.list.push({ text: this._taskInput.value });
this.setState(state);
}
you are directly editing the state with that push function, you should never do this in react as it will lead to unexpected consequences, this is how you should update the state:
if (this._tarefaInput.value !== ""){
//use the spread operator (...) to create a copy of the list in the state
const newList = [... state.list];
// push the new element tot the new list
newList.push({ text: this._taskInput.value });
// update the state
this.setState({list: newList});
}
Now the error you are getting is likely happening because somewhere in your code (possibly inside <Table/>) you are trying to print each element of the list array as a react component. You haven't shared the part where the list is rendered, but I'm guessing you are doing something like this:
//somewhere inside a render:
{
list.map(Element => <Element />);
}
//Proper way of doing it
{
list.map(element => <p>{element.text}</p>);
}
I can try to help more if you share more of your code and the entitre log with the the error description (with file and line number)

The issue is this line inside your render: {this.state.list}. You can render an array, but you can't render an object. The solution is to map over the array and output some JSX such as the following. Let's assume you have a list of objects with a name and id property:
{this.state.list.map(item => (<div key={item.id}>{item.id}</div>))}

Related

React | Adding and deleting object in React Hooks (useState)

How to push element inside useState array AND deleting said object in a dynamic matter using React hooks (useState)?
I'm most likely not googling this issue correctly, but after a lot of research I haven't figured out the issue here, so bare with me on this one.
The situation:
I have a wrapper JSX component which holds my React hook (useState). In this WrapperComponent I have the array state which holds the objects I loop over and generate the child components in the JSX code. I pass down my onChangeUpHandler which gets called every time I want to delete a child component from the array.
Wrapper component:
export const WrapperComponent = ({ component }) => {
// ID for component
const { odmParameter } = component;
const [wrappedComponentsArray, setWrappedComponentsArray] = useState([]);
const deleteChildComponent = (uuid) => {
// Logs to array "before" itsself
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5"},
{"uuid":"0ed3cc3-7cd-c647-25db-36ed78b5cbd8"]
*/
setWrappedComponentsArray(prevState => prevState.filter(item => item !== uuid));
// After
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5",{"uuid":"0ed3cc3-
7cd-c647-25db-36ed78b5cbd8"]
*/
};
const onChangeUpHandler = (event) => {
const { value } = event;
const { uuid } = event;
switch (value) {
case 'delete':
// This method gets hit
deleteChildComponent(uuid);
break;
default:
break;
}
};
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
onChangeOut: onChangeUpHandler,
};
setWrappedComponentsArray(wrappedComponentsArray => [...wrappedComponentsArray, objToAdd]);
// Have also tried this solution with no success
// setWrappedComponentsArray(wrappedComponentsArray.concat(objToAdd));
};
return (
<>
<div className='page-content'>
{/*Loop over useState array*/}
{
wrappedComponentsArray.length > 0 &&
<div>
{wrappedComponentsArray.map((props) => {
return <div className={'page-item'}>
<ChildComponent {...props} />
</div>;
})
}
</div>
}
{/*Add component btn*/}
{wrappedComponentsArray.length > 0 &&
<div className='page-button-container'>
<ButtonContainer
variant={'secondary'}
label={'Add new component'}
onClick={() => addOnClick()}
/>
</div>
}
</div>
</>
);
};
Child component:
export const ChildComponent = ({ uuid, onChangeOut }) => {
return (
<>
<div className={'row-box-item-wrapper'}>
<div className='row-box-item-input-container row-box-item-header'>
<Button
props={
type: 'delete',
info: 'Deletes the child component',
value: 'Delete',
uuid: uuid,
callback: onChangeOut
}
/>
</div>
<div>
{/* Displays generated uuid in the UI */}
{uuid}
</div>
</div>
</>
)
}
As you can see in my UI my adding logic works as expected (code not showing that the first element in the UI are not showing the delete button):
Here is my problem though:
Say I hit the add button on my WrapperComponent three times and adds three objects in my wrappedComponentsArray gets rendered in the UI via my mapping in the JSX in the WrapperComponent.
Then I hit the delete button on the third component and hit the deleteChildComponent() funtion in my parent component, where I console.log my wrappedComponentsArray from my useState.
The problem then occurs because I get this log:
(2) [{…}, {…}]
even though I know the array has three elements in it, and does not contain the third (and therefore get an undefined, when I try to filter it out, via the UUID key.
How do I solve this issue? Hope my code and explanation makes sense, and sorry if this question has already been posted, which I suspect it has.
You provided bad filter inside deleteChildComponent, rewrite to this:
setWrappedComponentsArray(prevState => prevState.filter(item => item.uuid !== uuid));
You did item !== uuid, instead of item.uuid !== uuid
Please try this, i hope this works
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item !== uuid));
};
After update
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item.uuid !== uuid)); // item replaced to item.uuid
};
Huge shoutout to #Jay Vaghasiya for the help.
Thanks to his expertise we managed to find the solution.
First of, I wasn't passing the uuid reference properly. The correct was, when making the objects, and pushing them to the array, we passed the uuid like this:
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
parentOdmParameter: odmParameter,
onChangeOut: function(el) { onChangeUpHandler(el, this.uuid)}
};
setWrappedComponentsArray([...wrappedComponentsArray, objToAdd]);
};
When calling to delete function the function that worked for us, was the following:
const deleteChildComponent = (uuid) => {
setWrappedComponentsArray(item => item.filter(__item => __item.uuid !== uuid)); // item replaced to item.uuid
};

ReactJS Classes How to pass State from Child Component to its Parent?

I will call the LI tags like this (li) so they are not made into bullet points for his question
Hi I am trying to send a Child component to its Parent in ReactJS.
I tried many things I managed to send the child component state back up to its Parent props but when the page renders I have a couple of (li) tags which I want the update to update with it for example like:
(li) hard coded text (/li)
(li) old text (/li)
(li) update prop (/li)
(li) update prop etc (/li)
but instead the update deletes all previous code so it looks like:
(li) update prop deleted all previous li's (/li)
Hope that made sense here is my code
Parent Component
import React from 'react';
import { generateId, getNewExpirationTime } from '../../util/utilities';
import Thought from '../Thought/Thought.js'
import AddThoughtForm from '../AddThoughtForm/AddThoughtForm.js'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
thoughts: [{
id: generateId(),
text: 'This is a place for your passing thoughts.',
expiresAt: getNewExpirationTime()
},
{
id: generateId(),
text: "They'll be removed after 15 seconds.",
expiresAt: getNewExpirationTime()
}]
};
this.addThought = this.addThought.bind(this);
this.removeThought = this.removeThought.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUpdate = this.componentDidUpdate.bind(this);
}
addThought(thought) {
console.log("LOOK")
console.log(thought)
console.log("DONE")
console.log(this.state.thoughts);
this.state.thoughts.push(thought);
this.setState(prevState => ({
...prevState,
thoughts: [thought]
}))
console.log("passed over")
console.log(this.state.thoughts);
}
removeThought(selected) {
//alert('selected');
let updatedThoughts = this.state.thoughts.filter((thought) => selected.id !== thought.id);
return this.setState({ thoughts: updatedThoughts })
}
render() {
return (
<div className="App">
<header>
<h1>Passing Thoughts</h1>
</header>
<main>
<AddThoughtForm
addThought={this.addThought}
thoughts={this.state.thoughts} />
<ul className="thoughts">
{(this.state.thoughts) && this.state.thoughts.map((thought, index) => (
<Thought
key={thought.id}
thought={thought}
removeThought={this.removeThought} />
))}
</ul>
</main>
</div>
);
}
}
Still on the Parent Component my .addThought(arg) is where the action is. This is where I'm sending the Child AddThoughtForm state object into it. By inside of .addThought() I am doing this:
addThought(thought) {
console.log("LOOK")
console.log(thought)
console.log("DONE")
console.log(this.state.thoughts);
this.state.thoughts.push(thought);
this.setState(prevState => ({
...prevState,
thoughts: [thought]
}))
console.log("passed over")
console.log(this.state.thoughts);
}
What happens is when I pass it over my previous State of my parent is deleted and replaced by this new information from my child component. How do I stop that? I want to only add this new Information to the previous info that the Parent state already have. here is the state from my parent:
constructor(props) {
super(props);
this.state = {
thoughts: [{
id: generateId(),
text: 'This is a place for your passing thoughts.',
expiresAt: getNewExpirationTime()
},
{
id: generateId(),
text: "They'll be removed after 15 seconds.",
expiresAt: getNewExpirationTime()
}]
};
this.addThought = this.addThought.bind(this);
this.removeThought = this.removeThought.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUpdate = this.componentDidUpdate.bind(this);
}
Now over to the Child Component
AddThoughtForm.js
import React from 'react';
import { generateId, getNewExpirationTime } from '../../util/utilities';
class AddThoughtForm extends React.Component {
constructor(props) {
super(props);
this.state = {
ideas: [this.props.thoughts] // I can take off []
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleTextChange(event) {
const { value } = event.target
this.setState(prevState => {
let thoughts = Object.assign({}, prevState.ideas); // creating copy of state variable
thoughts.id = generateId();
thoughts.text = value; // update the name property, assign a new value
thoughts.expiresAt = getNewExpirationTime();
return { thoughts }; // return new object
})
console.log(this.state.ideas)
}
handleSubmit(event) {
event.preventDefault();
this.props.addThought(this.state.thoughts)
alert(this.state.ideas);
}
render() {
return (
<form className="AddThoughtForm" onSubmit={this.handleSubmit}>
<input
type="text"
aria-label="What's on your mind?"
placeholder="What's on your mind?"
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Add" />
</form>
)
}
}
export default AddThoughtForm;
On my .handleTextChange(event) is where I am linking it with my input tag in render so what I am doing what ever Is typed into it I want entered I want it be passed to my Parent Component. well It is passed over but it over-writes the old info every time the old (li) and it is then all just a new li being rendered. Any ideas on how I can fix all of this?
handleTextChange(event) {
const { value } = event.target
console.log(value)
this.setState(prevState => {
let thoughts = Object.assign({}, prevState.ideas); // creating copy of state variable
thoughts.id = generateId();
thoughts.text = value; // update the name property, assign a new value
thoughts.expiresAt = getNewExpirationTime();
return { thoughts }; // return new object
})
console.log(this.state.ideas)
}
I managed to fix it
just had to add on the Parent Component a thoughts: [...prevState, thought] as I was overwritting the old thoughts with the new incoming thought
Like this:
In the method, .addThought()
this.setState(prevState => ({
...prevState,
thoughts: [...prevState.thoughts, thought]
}))

Getting an error .map() is not a function

I am stuck at this error ".map is not a function"
TypeError
teachers.map is not a function
class toggleView extends React.Component {
constructor(props) {
super(props);
this.state = {
teachers: ["Willis", "Duke", "Davis", "Walter"],
showPersons: false
};
this.handleView = this.handleView.bind(this);
}
handleView = e => {
e.preventDefault();
let teachers = this.state.teachers;
this.setState({
teachers: !teachers
});
};
render() {
let teachers = this.state.teachers;
let individualTeacher = teachers.map(teacher => <li> {teacher} </li>);
let person = null;
if (this.state.showPersons === true) {
person = <li>{individualTeacher}</li>;
} else {
person = null;
}
return (
<div>
<h3> Heloo folks </h3>
<button onClick={this.handleView}>Toggle View</button>
<br />
<br />
<ul>{person}</ul>
</div>
);
}
}
code snippet link
.map is not a function
Is very common error message, it means that the object you are looping through is not an Array.
What you did in your code, is that when you click on the button and trigger the handleView you changed your teachers list in the state from a valid Array to something of boolean type:
let teachers = this.state.teachers;
this.setState({
teachers: !teachers // here
});
After changing the state, React will quickly render the new state, but then find himself looping through a boolean instead of Array, thus trigger the error you are seeing.
Update
As one of the answers guessed, I think you are trying to change showPersons instead of teachers Array.
With this code, you assign a boolean to the previous array value. You cannot use the map function with a boolean
this.setState({
teachers: !teachers
});
teachers is an array, on handleView you assign a boolean instead of an array, so in next render you trying to #Array.map() a boolean value which caused a runtime error.
// teachers is an array
let teachers = this.state.teachers;
// teachers will be a boolean after the render
// ![] === false
this.setState({
teachers: !teachers,
});
I think you meant to use showPersons instead.
handleView = (e) => {
e.preventDefault();
let showPersons = this.state.showPersons;
this.setState({
showPersons: !showPersons,
});
};

Double setState method in one function

I am trying to create a autocomplete component. It's an input where user types the countru name and if letters match name of some country, the hints are displayed.
In my App Component i have method handleChange Within this method i change my state two times, which is bad idea.
How can I split it to change state in distinct methods ?
import React, { Component } from 'react';
import AutoComplete from './autoComplete.jsx';
import data from './data.json';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
resoults: []
}
}
handleChange() {
let inputValue = this.refs.input.value;
this.setState({
inputValue: inputValue
});
let regular = "^" + this.state.inputValue;
let reg = new RegExp(regular , "i");
let filtered = data.filter((i,index)=> {
return (reg.test(i.name)
);
});
console.log(filtered);
this.setState({resoults:filtered})
}
render() {
return (
<div>
<input onChange={this.handleChange.bind(this)} type="text" ref="input"/>
<h3>You typed: {this.state.inputValue}</h3>
<AutoComplete resoults={this.state.resoults} />
</div>
);
}
}
export default App;
import React, {Component} from 'react';
class AutoComplete extends Component {
render() {
return (
<div>
<h4>autocompleteComponent</h4>
{this.props.resoults.map((i)=> {
return (
<ul>
<li>{i.name}</li>
</ul>
);
})}
</div>
);
}
}
export default AutoComplete;
I found myself in this position many times, but I got to the conclusion that it's better to compute the autocomplete options (in your case) without having them in the state of your component.
As I have used them until now, the state and props of a component should represent minimal data needed to render that specific component. Since you have your input value in the state, having the autocomplete options there also seems redundant to me. So here is what I propose:
class App extends Component {
this.state = {
inputValue: '',
};
handleChange(e) {
const inputValue = e.target.value;
this.setState({
inputValue,
});
}
computeResults() {
const {inputValue} = this.state;
// your functionality for computing results here
}
render() {
const {inputValue} = this.state;
const results = this.computeResults();
return (
<div>
<input type="text" onChange={this.handleChange.bind(this)} value={inputValue} />
<h2>You typed: {inputValue}</h2>
<Autocomplete results={results} />
</div>
);
}
}
Notes
Since your results come synchronously, via the .json import, this seems the perfect solution to me. If you want to get them via fetch or anything else, then you'll have to figure out a slightly different approach, but keep in mind that the state of your component should not contain redundant data.
Stop using ref with string value! and use refs when there is absolutely no other way because a React component should not generally deal with DOM operations directly. If you really need to use refs, use ref callbacks.
Hope this helps!
Use another function and setState callBack:
handleChange() {
let inputValue = this.refs.input.value;
this.setState(
{
inputValue: inputValue
},
() => this.secondFunc()
);
}
secondFunc() {
let regular = '^' + this.state.inputValue;
let reg = new RegExp(regular, 'i');
let filtered = data.filter((i, index) => {
return reg.test(i.name);
});
console.log(filtered);
this.setState({ resoults: filtered });
}

Uncaught TypeError: Cannot read property of null, removing element from array javascript

I am trying to remove an element from an array when the onClick on the remove button is called, when the button is clicked I am getting Uncaught TypeError: Cannot read property 'name' of null
Why am I getting this error?
removeData(key) {
console.log(key);
const data = this.state.data;
data[key] = null;
this.setState({ data });
}
renderData(key){
const user = this.props.data[key];
console.log(user.name);
console.log(user.id);
return(
<div key={key}>
<li> <strong> Name: </strong> {user.name},
<strong> ID: </strong> {user.id} </li>
<button onClick={() => this.props.removeData(key)}> Remove </button>
</div>
)
}
Why am I getting this error?
You are explicitly setting data[key] to null:
data[key] = null;
When the component rerenders it presumably calls renderData with the key that was "removed" (because the property is still there, its value is just null). user will be null and accessing null.name throws an error.
Depending on what you actually want, either skip null values in your renderData method, or actually delete the entry with
delete data[key];
Assigning null does not delete a property:
var obj = {foo: 42};
// Doesn't actually remove the property
obj.foo = null;
console.log(obj);
// This removes the property
delete obj.foo;
console.log(obj);
I am trying to remove an element from an array
If you really have an array, then there more things wrong. {...this.state.data} is not an appropriate way to clone an array because you end up with an object.
To properly perform this action on an array you would do
removeData(key) {
// Assuming `key` is the index of the element
// Create copy
const data = Array.from(this.state.data);
// Remove element at index `key`
data.splice(key, 1);
this.setState({ data });
}
Try:
renderData(key){
const user = this.props.data[key] || {}; // change
console.log(user.name);
console.log(user.id);
return(
<div key={key}>
<li> <strong> Name: </strong> {user.name},
<strong> ID: </strong> {user.id} </li>
<button onClick={() => this.props.removeData(key)}> Remove </button>
</div>
)
It seems like your data isn't ready when the component renders, so putting in a blank object will allow the component to render empty while the data loads.
edit: You could consider:
renderData(key){
if (!this.props.data[key]) return null; //change
const user = this.props.data[key];
console.log(user.name);
console.log(user.id);
return(
<div key={key}>
<li> <strong> Name: </strong> {user.name},
<strong> ID: </strong> {user.id} </li>
<button onClick={() => this.props.removeData(key)}> Remove </button>
</div>
)
This would hide the component as the data is being fetched. It's up to you then to communicate that the data is loading to the user. You could pass down a loading prop that is set to true when the request is fired and set to false when the request returns data and show either some loading text or an animation.
Removing objects from arrays
A good way to remove objects from arrays is to use filter. Using filter keeps us safe from bugs that come from using the index inside of the map function. data.map((item,index) => <div key={index} onClick={this.removeData(index)}> {item.fruit} </div>). This can cause serious problems when removing objects from the array.
What react says about this.
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
The Filter Approach
This approach takes whatever id the object has and returns every object that has a different id.
this.setState({ data: temp.filter(item => item.id !== id) });
Working Example
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends Component {
constructor() {
super();
this.state = {
data:[
{ fruit: "apple", id:1 },
{ fruit: "orange", id:2 },
{ fruit: "pineapple", id:3 },
],
}
}
removeData = (id) => {
const { data } = this.state;
const temp = data.slice();
this.setState({ data: temp.filter(item => item.id !== id) });
}
renderData = () =>{
const { data } = this.state;
return data.map((item) => {
return <div key={item.id}>
<label> {item.fruit} </label>
{item.id}
<button onClick={() => this.removeData(item.id)}> Remove </button>
</div>
})
}
render() {
return (
<div>
{this.renderData()}
</div>
);
}
}
render(<App />, document.getElementById('root'));

Categories

Resources