Getting an error .map() is not a function - javascript

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

Related

Why my setState doesn't accept this object?

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

How do I reset a specific part of a state with onClick in React

I have an application that when a user clicks a button it loads a data set. When another button is clicked, a different data set loads. I am trying to clear the data set each time a button is clicked. So if I click button 2, the state for button 1 is reset to [] (the data is an array)
Here is my code:
class Home extends React.Component {
handleCopyTrack = (event) => {
event.preventDefault();
this.setState({ courthouse: [] });
let args = {
selected_site_id: this.props.authenticate.selected_site_id,
site_name: this.props.authenticate.site_name,
sec_organization_id: this.props.authenticate.selected_sec_organization_id,
sec_user_name: this.props.authenticate.sec_user_name,
};
this.props.loadCopyTrackInfo(args);
};
handleCourtHouse = (event) => {
event.preventDefault();
this.setState({ copytrack: [] });
let args = {
selected_site_id: this.props.authenticate.selected_site_id,
site_name: this.props.authenticate.site_name,
sec_organization_id: this.props.authenticate.selected_sec_organization_id,
sec_user_name: this.props.authenticate.sec_user_name,
};
this.props.loadCourtHouseInfo(args);
};
This part doesn't work: this.setState({ courthouse: [] });
What am I missing?
I didn't see your state's initialization. Do you hava a constructor?
Also setState is an async function.
If you want to change a state and then use that state with changed value, you have to call an other function in setState's callback.
const handleClick = this.setState({ courthouse: [] }, this.handleClickButtonOperations());
Here is how I solved it:
I set local state:
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
showCopytrack: false,
showCourtHouse: false,
};
}
Then when an event is called, I update the alternating state:
handleCopyTrack = (event) => {
event.preventDefault();
this.setState({ showCopytrack: true });
this.setState({ showCourtHouse: false });
Then I show or hide based on what is set to true or false:
{this.state.showCopytrack === true ? (
I would recommend switching to hooks:
const Home = ({authenticate, loadCopyTrackInfo, loadCourtHouseInfo}) => {
const [courtHouse, setCourtHouse] = useState([]);
const [copyTrack, setCopyTrack] = useState([]);
function handleCopyTrack(event) {
event.preventDefault();
//Why you even need to clear this array here? Initial state will be empty array so after click new data will be added so no need to clearing
//setCourtHouse([]);
//you can pass here authenticate object
return loadCopyTrackInfo(authenticate);
};
function handleCourtHouse(event) {
event.preventDefault();
//setCopyTrack([]);
return loadCourtHouseInfo(authenticate);
};

set multiple states, and push to state of array in one onClick function

I'm running into a recurring issue in my code where I want to grab multiple pieces of data from a component to set as states, and push those into an array which is having its own state updated. The way I am doing it currently isn't working and I think it's because I do not understand the order of the way things happen in js and react.
Here's an example of something I'm doing that doesn't work: jsfiddle here or code below.
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
}
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
setCategoryStates = (categoryTitle, categorySubtitle) => {
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
render() {
return (
<CategoryComponent
setCategoryStates={this.setCategoryStates}
categoryTitle={'Category Title Text'}
categorySubtitle={'Category Subtitle Text'}
/>
);
}
}
class CategoryComponent extends Component {
render() {
var categoryTitle = this.props.categoryTitle;
var categorySubtitle = this.props.categorySubtitle;
return (
<div onClick={() => (this.props.setCategoryStates(
categoryTitle,
categorySubtitle,
))}
>
<h1>{categoryTitle}</h1>
<h2>{categorySubtitle}</h2>
</div>
);
}
}
I can see in the console that I am grabbing the categoryTitle and categorySubtitle that I want, but they get pushed as null into this.state.categoryArray. Is this a scenario where I need to be using promises? Taking another approach?
This occurs because setState is asynchronous (https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly).
Here's the problem
//State has categoryTitle as null and categorySubtitle as null.
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
//This gets the correct values in the parameters
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
//This method is using the state, which as can be seen from the constructor is null and hence you're pushing null into your array.
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
Solution to your problem: pass callback to setState
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
}, () => {
/*
Add state to the array
This callback will be called once the async state update has succeeded
So accessing state in this variable will be correct.
*/
this.pushToCategoryArray()
})
}
and change
pushToCategoryArray = () => {
//You don't need state, you can simply make these regular JavaScript variables
this.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
I think React doesn't re-render because of the pushToCategoryArray that directly change state. Need to assign new array in this.setState function.
// this.state.categoryArray.push({...})
const prevCategoryArray = this.state.categoryArray
this.setState({
categoryArray: [ newObject, ...prevCategoryArray],
)}

Unable to change state property when clicking on child - React

I am wanting to change the colour of text when I click on it by triggering a function in a child element, such that it affects the state of the parent - As a particular parent state property determines whether the text is one colour or another.
I know that the function fired in the child DOES feed back up to the parent, as I can get the parent to console.log properly. However, I can't seem to get it to change the state property.
PS. I would like to use a "(prevState) =>" if this is applicable
Parent
const tasks = [
{ name: 'task1', isComplete: false },
{ name: 'task2', isComplete: true },
{ name: 'task3', isComplete: false },
]
class App extends React.Component {
constructor() {
super();
this.state = {
tasks
}
}
...
toggleTask(taskToToggle) {
const foundTask = tasks.find(task => task.name === taskToToggle.name)
foundTask.isComplete !== foundTask.isComplete;
this.setState({ tasks: this.state.tasks })
}
...
Child
return (
<div key={name} style={taskStyle} onClick={this.handleToggleComplete.bind(this)}>
{name}
</div>
)
handleToggleComplete() {
const taskToToggle = this.props;
this.props.toggleTask(taskToToggle);
}
You are returning the same array and task object.
So that current state tasks is the same array as next state tasks containing the same objects.
You need to return another array for example using Array.prototype.map
toggleTask(taskToToggle) {
this.setState(({tasks}) => ({
tasks: tasks.map(task =>
task.name === taskToToggle.name ? {...task, isComplete: !task.isComplete} : task)
}))
}
in your child onClick pass the this.props.toggleTask and bind the name the task directly to it like :
onClick={this.props.toggleTask.bind(name)}
And in your Parent in the function dont use taskToToggle.name but just "name" :
const foundTask = tasks.find(task => task.name === name)
Or you can just change completly the function to make it easier like :
toggleTask(name){
const updated = tasks.map(task => {
if(task.name === name){
task.isComplete = !task.isComplete
}
return task
})
this.setState({tasks: updated})
}
I think you need to calculate the final state before assigning the state
change your
this.setState({ tasks: this.state.tasks }) //doesn't change anything
to
let finalState = Object.assign({}, ...this.state.tasks, foundTask)
this.setState({ tasks: finalState }) //new object gets assigned to state
#Chris was correct in pointing out my n00bish error.
foundTask.isComplete !== foundTask.isComplete
Should have been
foundTask.isComplete = !foundTask.isComplete
This now works.

React this.props don't update

Can someone help me to understand why this.props doesn't update after i filter it?
Here the slim version of my code
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterValue: 'all',
isHidden: true,
autoOptValue: ''
}
}
handleOnChangeBrand(evt) {
let selectedValue = evt.target.value;
this.setState({optionValue: selectedValue});
let filtered = this.props.autos.filter((auto) => {
if(auto.brands){
return auto.brands[0] === selectedValue;
}
return false;
});
console.log(this.props.auto) // still same number
console.log(filtered) // less autos. Actual filtered array
}
render() {
let autoDetail = this.props.autos.map(auto => {
return (
<Auto
key={auto.id}
id={auto.id}
name={auto.name}
brands={auto.brands ? auto.brands : false}/>
);
});
return (
<div>
<section>
<select id='autoFilter' className={this.state.isHidden ? 'u-is-hidden' : ''} onChange={this.handleOnChangeBrand.bind(this)} value={this.state.autoOptValue}>
<option value='brand1'> brand1 </option>
<option value='brand2'> brand2 </option>
</select>
</section>
<ul>
{autoDetail}
</ul>
</div>
);
}
So basically i have this.prop.auto is an array of 100 auto, each of them is an object with brand (which is another array) with 2,3 brands each.
I was able to filter, since filtered give me back an array with filtered autos, the correct ones.
But after that, this.props.auto doesn't update, nor does the UI.
I did something similar but sorting the auto by the brands and it works smoothly.
I don't get the difference here
this.props is effectively immutable within a component, so you cannot update the value of this.props.autos. Array#filter is also a pure function, so the array being filtered is not altered, but a new filtered array is returned. This is why when you log filtered in your function you see the filtered array, but this.props.autos is unchanged.
The simple answer to this is to do the filtering within your render method - I have added an initial state for optionValue of false, and within the filter method checked for this and not filtered if it is still false.
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterValue: 'all',
isHidden: true,
autoOptValue: '',
optionValue: false
}
}
handleOnChangeBrand(evt) {
let selectedValue = evt.target.value;
this.setState({optionValue: selectedValue});
}
render() {
const { optionValue } = this.state;
const autoDetail = this.props.autos
.filter((auto) => {
if (!optionValue) return true;
if(auto.brands){
return auto.brands[0] === optionValue;
}
return false;
})
.map(auto => {
return (
<Auto
key={auto.id}
id={auto.id}
name={auto.name}
brands={auto.brands ? auto.brands : false}/>
);
});
return (
<div>
<section>
<select id='autoFilter' className={this.state.isHidden ? 'u-is-hidden' : ''} onChange={this.handleOnChangeBrand.bind(this)} value={this.state.autoOptValue}>
<option value='brand1'> brand1 </option>
<option value='brand2'> brand2 </option>
</select>
</section>
<ul>
{autoDetail}
</ul>
</div>
);
}
Filter returns a new array. This is so you still have the original on hand. There are so very, very many reasons this is a good thing, and practically 0 reasons it's a bad thing.
Props is meant to be immutable (just like arrays that you call filter on). Don't change the members on props. Nor should you change the members of the members of props, or any descendant data of props in general.
That is why state exists, so if you absolutely must, you can save it there.
Regarding #2, typically you should be pulling that stuff out into higher and higher layers of abstraction, getting away from the view data, completely, and just passing in finished data that's ready for showing.
The Array.prototype.filter() method always returns a new filtered array without changing the old one:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
In your handleOnChangeBrand event, you're creating a filtered array, without affecting the old one, but then not using that filtered array when React calls render for the second time.
A small example of how you could handle this would be as follows:
1) Have react render your default autos prop
export default class AutoList extends React.Component {
render() {
const autoDetail = this.props.autos.map(auto => {
return (
<Autos ... />
)
});
return (
<ul>
{ autoDetail }
</ul>
);
}
}
2) Add in a click handler and a state value to hold the value what you would like to filter by:
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: '' // this is where we will store what we want to filter by
}
};
// All this function will do is update the state which we want to filter by
// this 'arrow function' auto bind 'this' for us, so we don't have to explicitely set .bind as you were doing before
handleOnChangeBrand = (event) => {
this.setState({
filter: event.target.value
})
};
render() {
const autoDetail = this.props.autos.map(auto => {
return (
<Autos ... />
)
});
return (
<ul>
{ autoDetail }
</ul>
);
}
}
3) Finally, we will use the value we are storing in state to filter to the auto brands we would like and use that to build our array
export default class AutoList extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: '' // this is where we will store what we want to filter by
}
};
getFilteredAutos = () => {
// if we have no filter, return everything!
if (this.state.filter === '') {
return this.props.autos;
}
// this is returning the newely filtered array, without affecting the old one
return this.props.autos.filter(auto => {
if(auto.brands){
// we're filtering by our saved value
return auto.brands[0] === this.state.filter;
}
return false;
});
},
handleOnChangeBrand = (event) => {
this.setState({
filter: event.target.value
})
};
render() {
// we're mapping by the filtered results here
const autoDetail = this.getFilteredAutos().map(auto => {
return (
<Autos ... />
)
});
return (
<ul>
{ autoDetail }
</ul>
);
}
}

Categories

Resources