After submit, b(array_name).map is not a function - ReactJS - javascript

I have started learning ReactJS and I am stuck on this error for a while now.
export default class Bag extends Component {
constructor(props){
super(props)
this.state = {
books : [
{
name : "Origin",
read : "Yes",
owner: "Yes"
},
{
name: "The Alchemist",
read: "Yes",
owner: "Yes"
},
{
name : "The Tale of Twisted Candle",
read : "Yes",
owner: "Yes"
},
{
name: "Martian",
read: "Yes",
owner: "Yes"
}
]
}
this.setStateHandler = this.setStateHandler.bind(this)
this.handleChange = this.handleChange.bind(this)
}
setStateHandler(){
this.setState({books: this.state.books })
}
handleChange(book){
console.log("Here is your book:")
console.log(book)
console.log(this.state.books)
let tempVal = {
name : book.name,
read : book.read,
owner: book.owner
}
this.setState({books: this.state.books.push(tempVal) })
console.log(this.state.books)
}
render(){
let b = this.state.books
return (
<div align="center">
<h1>{this.props.name}'s Bag</h1>
<table>
<tbody>
<tr>
<th>Book Name</th>
<th>Read</th>
<th>Ownership</th>
</tr>
</tbody>
<tbody>
{ b.map(function(book){
return <Book name={book.name} read={book.read} owner={book.owner}/>
})}
</tbody>
</table>
<BookForm name="Book" read="No" owner="No" onChange={this.handleChange} />
<Button />
</div>
)
}
}
When code is run for first time, everything works fine. But when I try to submit a new book, an error is thrown.
TypeError: b.map is not a function
While looking for the solution in other similar questions, they all referred that the map function is for Array not Object. So, I have checked that too. Apparently, the new value of 'b' after submit is still an Array.

The line:
this.setState({books: this.state.books.push(tempVal) })
in your handleChange method is likely the problem. The push method returns a number, so that line is actually setting the value of books in your state to a number, not an array. You might try changing it to:
this.setState({books: this.state.books.concat([tempVal]) })
Or, if you want to use the functional setState style:
this.setState(prevState => {
prevState.books.push(tempVal);
return prevState;
});

Related

Problem to delete a row from a table with React

I'm trying to figure out how to remove a row from a simple table using a simple button.
I try to use the index but with what I wrote, when I click on the line to delete, it is only all the others that are deleted ...
I guess the problem comes from the way I use the index but I have some difficulties to understand the behavior.
let users = [
{ firstName: "John", lastName: "Connor", age: 20 },
{ firstName: "Geralt", lastName: "Rivia", age: 45 },
{ firstName: "Nathan", lastName: "Drake", age: 36 }
]
class exercice extends React.Component {
constructor(props){
super(props);
this.state = {
users: users
}
}
onClickDeleteRow = index => {
users = users.splice(index,1)
this.setState({users: users})
console.log(this.state.users)
}
render() {
let usersName = this.state.users.map((user, index) => (
<tr key={index}>
<td>{user.firstName} </td>
<td>{user.lastName} </td>
<td><button onClick={() => this.onClickDeleteRow(index)}>Delete</button></td>
</tr>
))
return (
<table>
<thead>
<tr>
<th> firstName </th>
<th> lastName</th>
</tr>
</thead>
<tbody>
{usersName}
</tbody>
</table>
)
}
}
ReactDOM.render(
<div className="firstContainer">
<Exercice />
</div>,
document.getElementById('root')
)
.splice() method returns an array containing the elements that were removed from the original array on which .splice() method was called. So if index is 1, then
users = users.splice(index,1)
will update users and assign an array containing one element to the users constant. If index is 1 then after the above statement, users will be
users = [{ firstName: "Geralt", lastName: "Rivia", age: 45 }]
which is what gets set in the state. That is why all other rows are removed except the one that you wanted to delete.
Solution
You could solve the problem in couple of ways:
Change
users = users.splice(index, 1);
to
users.splice(index, 1);
this will make sure that you don't update the users with the return value of .splice() method.
Problem with the first approach is that it modifies the original users array. You could avoid modifying the users array by using the .filter() method.
Change the onClickDeleteRow method as shown below:
onClickDeleteRow = (index) => {
const updatedUsers = this.state.users.filter((user, idx) => idx !== index);
this.setState({ users: updatedUsers });
};
I think you should call this.state.users in the method
onClickDeleteRow = index => {
const users = this.state.users;
users.splice(index, 1);
this.setState({ users: users });
};

Using react, how would I hide a table row on click?

Hey guys I am using this table to display data and I added a button to each row. How would I be able to hide a row when I click the hide button next to it?
I am aware of a way to do within html elements but not sure how to hide a particular row within a table thats within a loop
Can anyone show me how to accomplish this?
Thank you
import React, { Component } from 'react'
class Table extends Component {
constructor(props) {
super(props) //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = { //state is by default an object
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com' },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com' },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com' },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com' }
]
}
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email } = student //destructuring
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
)
})
}
renderTableHeader() {
let header = Object.keys(this.state.students[0])
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
render() { //Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id='title'>React Dynamic Table</h1>
<table id='students'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
}
export default Table
You could add an onClick handler to the button that adds a property that determines the student should be hidden or not.
Notice the onClick={() => this.hideRow(id)} below.
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isHidden } = student; //destructuring
// isHidden will default to undefined if not found on the student object
// user is hidden
if (isHidden === true) {
return null;
}
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td>
<button onClick={() => this.hideRow(id)}>HIDE</button>
</td>
</tr>
);
});
}
The hideRow method will accept a student id and will add an isHidden: true attribute to the student with that id.
hideRow(id) {
const students = this.state.students.map((student) => {
// not same id? leave as is
if (student.id !== id) {
return student;
}
return { ...student, isHidden: true };
});
this.setState({ students });
}
Now you don't want to display the isHidden column, so you have to update renderTableHeader method to skip that.
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
// notice this
if (key === "isHidden") {
return null;
}
return <th key={index}>{key.toUpperCase()}</th>;
});
}
Add a isVisible key in all objects like
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com', isVisible: true },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com', isVisible: true },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com', isVisible: true },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com', isVisible: true }
]
Then in your render row function do this
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isVisible } = student
return isVisible ? (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
) : null
})
On button/row click update state.
Try this code
import React, { Component } from "react";
class Table extends Component {
constructor(props) {
super(props); //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = {
//state is by default an object
students: [
{ id: 1, name: "Wasif", age: 21, email: "wasif#email.com", toggle: true},
{ id: 2, name: "Ali", age: 19, email: "ali#email.com", toggle: true },
{ id: 3, name: "Saad", age: 16, email: "saad#email.com", toggle: true},
{ id: 4, name: "Asad", age: 25, email: "asad#email.com", toggle: true }
]
};
}
handleClick(index) {
let students = [...this.state.students];
students[index].toggle = !students[index].toggle;
this.setState({ students });
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, toggle } = student; //destructuring
if (toggle) {
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<`td`>
<button
value={index}
onClick={(e) => this.handleClick(e.target.value)}
>
Hide
</button>
</td>
</tr>
);
} else {
return null;
}
});
}
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>;
});
}
render() {
//Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id="title">React Dynamic Table</h1>
<table id="students">
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
);
}
}
export default Table;
Follow these steps:
Put an onclick on the button
Pass the array as props to the component
On the next component display the array
Add the onclick method to it which is also passed as a props from the main component(Pass id as a parameter)
In the method use a filter array to remove the row of your choice when you click it.
The code is as follow:
https://codesandbox.io/s/modern-tdd-mlmzl?file=/src/components/Table.js

Make POST request and update DB for each and every user in Child Component, using React Life Cycle Method

Here Table shows the previous month user salary details. When click the "Update" button, system will retrieve the necessary data for this month and calculate the new salary and properties and will update the child component table values. Child component has other Child Component Buttons too.
When updating the table raws with new values "Need to make a post request for each and every user and update the database iterately". Here infinity looping happening(infinity POST request for update DB) when render child component and its children.
Could you please suggest a way to update each and every user details to the database. The way to call Redux action function(this.props.updateUserLog(newUserLog.handle, userDetails)) inside the child component "RowComponent". When re-rendering it's children, the POST request must not send looping.
~ Parent Component ~
import { getDriverCommissionAlcohol } from "../redux/actions/dataActions";
class DriverPerfomance extends Component {
constructor(props = {}) {
super(props);
this.state = {
press: false,
};
}
UpdatePerformance = (event) => {
this.setState({ press: true });
this.props.getDriverCommissionAlcohol(month, year);
};
render() {
const {
data: {
drivers: { user, month, year, createdAt },
performance: { driverCommission, alcoholStatus },
},
UI: { loadingOffScrean },
} = this.props;
let DriverCommissionResults = {};
if (this.state.press) {
let combinedUser = {};
let recent = [];
if (Object.keys(DriverCommissionResults).length > 0) {
combinedUser.forEach((filteredPerson) => {
recent.push(
<RowComponent
key={filteredPerson.userId}
handle={filteredPerson.username}
monthRetrive={this.state.month}
yearRetrive={this.state.year}
month={month}
year={year}
drunkenPesentage={filteredPerson.drunkenPesentage}
press={true}
newMonthCalculationDone={true}
/>
);
});
} else {
recent = (
<Fragment>
{user.map((filteredPerson) => (
<RowComponent
key={filteredPerson.userId}
handle={filteredPerson.username}
month={month}
year={year}
press={false}
newMonthCalculationDone={false}
/>
))}
</Fragment>
);
}
}
return (
<Fragment>
<Button disabled={loadingOffScrean} onClick={this.UpdatePerformance}>
Update
</Button>
<table>
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody>{recent}</tbody>
</table>
</Fragment>
);
}
}
~ Child Component ~
import { updateUserLog } from "../redux/actions/dataActions";
class RowComponent extends Component {
constructor(props) {
super(props);
this.state = {
handle: "",
createdAt: "",
ranking: 0,
year: "",
month: "",
};
}
componentWillReceiveProps() {
const newUserLog = {
handle: this.props.handle,
createdAt: new Date().toISOString(),
ranking: NewRankingCalculate,
year: this.props.yearRetrive ? this.props.yearRetrive : this.props.year,
month: this.props.monthRetrive ? this.props.monthRetrive : "",
};
this.mapUserDetailsToState(newUserLog);
}
mapUserDetailsToState = (newUserLog) => {
this.setState({
handle: newUserLog.handle ? newUserLog.handle : "",
createdAt: newUserLog.createdAt ? newUserLog.createdAt : "",
ranking: newUserLog.ranking ? newUserLog.ranking : "",
year: newUserLog.year ? newUserLog.year : "",
month: newUserLog.month ? newUserLog.month : "",
});
const userDetails = {
handle: newUserLog.handle,
createdAt: newUserLog.createdAt,
ranking: newUserLog.ranking,
year: newUserLog.year,
month: newUserLog.month,
};
this.props.updateUserLog(newUserLog.handle, userDetails);
};
render() {
const {
member: { username, year, month, salary },
} = this.props;
let action = (
<DrunkenLog
handle={username}
month={this.state.month !== "" ? this.state.month : month}
year={this.state.year !== "" ? this.state.year : year}
/>
);
<tr>
<td>{initialSalary}</td>
<td>{this.state.salary !== 0 ? this.state.salary : salary}</td>
<td>{action}</td>
</tr>;
}
}
Expectation:
Update DB table for each and every user, by calling POST requests function inside the child component life cycle methods. Stop the infinity looping POST requests. And make post request once changing the props.
i've noticed that if (Object.keys(DriverCommissionResults).length > 0) expression in ParentComponent will always be false, right? because DriverCommissionResults is just an empty object, initialised two rows before this check :)
try extend RowComponent from PureComponent, this will ensure that RowComponent will rerender only if some of props really changed (see docs: https://reactjs.org/docs/react-api.html#reactpurecomponent)
but i don't like the whole idea of what you are doing here.
You are basically change state of ParentComponent on button click, and make side effect (call redux in this case) when component is receiving props.
I would suggest:
in ParentComponent - make side effect (update DB) right in the middle of Button.onClick (keeping state changes, because you need some sort of wait indicator maybe).
in RowComponent - if you are doing some side effects - better place for them is componentDidMount or componentDidUpdate (but in second place you better always check for props to really differ from previous ones!)

Make table with filter in react failed

I'm able to make the filter works but simply use the filter of es6, but then the problem is I don't know how to reset the state back to the original source.
Usually the data source is an API calls but it possible to make avoid api call when the user deleted the value from the filter input?
const data = [
{
Id: "1",
FirstName: "Luke",
LastName: "Skywalker"
},
{
Id: "2",
FirstName: "Darth",
LastName: "Vader"
},
{
Id: "3",
FirstName: "Leia",
LastName: "Organa"
},
{
Id: "4",
FirstName: "Owen",
LastName: "Lars"
}
];
class App extends React.Component {
constructor() {
super()
this.state = {
data: data
}
}
filterId(e) {
const Id = e.target.value
if (Id) {
this.setState({
data: this.state.data.filter(v => v.Id === Id),
})
} else {
this.setState({
data
})
}
}
render() {
return (
<div style={styles}>
<table>
<th>Id <input type="number" onChange={e => this.filterId(e)} /></th>
<th>Name<input /></th>
{this.state.data.map((o, i) => {
return (
<tr>
<td>{o.Id}</td>
<td>{o.FirstName}</td>
</tr>
)
})}
</table>
</div>
);
}
}
https://codesandbox.io/s/oo22451v25
You may either store filtered data in separate variable or do data filtering dynamically in render function, like this.state.data.filter(v => v.Id === this.state.Id).map(...).
First up: You're going to want to filter using includes() for usability. Otherwise things won't match until they're 100% identical. This will matter once you start to deal with fields longer than one digit.
Secondly: Filtration via the render() method will allow you to search more robustly, ie allow you to backspace to unfilter, as this.state.data will remain pristine.
See below for a practical example.
// Data.
const data = [
{
Id: "1",
FirstName: "Luke",
LastName: "Skywalker"
},
{
Id: "2",
FirstName: "Darth",
LastName: "Vader"
},
{
Id: "3",
FirstName: "Leia",
LastName: "Organa"
},
{
Id: "4",
FirstName: "Owen",
LastName: "Lars"
}
]
// Filter.
class Filter extends React.Component {
// Constructor.
constructor(props) {
super(props)
this.state = {data, query: ''}
}
// Render.
render() {
return (
<div>
<table>
<tr>
<input placeholder="Query" type="number" value={this.state.query} onChange={event => this.setState({query: event.target.value})} />
</tr>
<th>Id</th>
<th>Name</th>
{this.state.data.filter((point) => point.Id.includes(this.state.query)).map((o, i) => {
return (
<tr>
<td>{o.Id}</td>
<td>{o.FirstName}</td>
</tr>
)
})}
</table>
</div>
)
}
}
ReactDOM.render(<Filter/>, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
There are several thing you could do, but I would advice not to change the original data stored in the state, but rather add a new state property which holds the data when the original data is filtered.
You could then assign this new state object to a variable and output it this way.
It could then look somethiing like this:
const data = [
{
id: '1',
firstName: 'Luke',
LastName: 'Skywalker',
},
{
id: '2',
firstName: 'Darth',
LastName: 'Vader',
},
{
id: '3',
firstName: 'Leia',
LastName: 'Organa',
},
{
id: '4',
firstName: 'Owen',
LastName: 'Lars',
},
];
class App extends React.Component {
constructor() {
super();
this.state = {
data: data,
filteredData: null,
};
this.filterId = this.filterId.bind(this);
}
filterId(e) {
const id = e.target.value;
if (id) {
this.setState({
filteredData: this.state.data.filter(v => v.id === id),
});
} else {
this.setState({
filteredData: null,
});
}
}
render() {
const dataOutput = this.state.filteredData || this.state.data;
return (
<div>
<table>
<th>
id <input type="number" onChange={e => this.filterId(e)} />
</th>
<th>
Name<input />
</th>
{dataOutput.map((o, i) => {
return (
<tr key={o.id}>
<td>{o.id}</td>
<td>{o.firstName}</td>
</tr>
);
})}
</table>
</div>
);
}
}
Oh, and one more thing: Use camelCase. Capital letters at the beginning should only be used when declaring classes.

Push data from multiple inputs to state array

My program generates few inputs and i try to push data to my state's array's
export default class TransmittersTable extends Component {
constructor() {
super()
this.state = {
axisX: [],
axisY:[],
power: [],
}
}
updateAxisX(e) {
this.setState({
axisX: this.state.axisX.push(e.target.value)
})
}
updateAxisY(e) {
this.setState({
axisY: this.state.axisY.push(e.target.value)
})
}
updateAxisPower(e) {
this.setState({
power: this.state.power.push(e.target.value)
})
}
generateTransmittersItems(){
let transmitters = [];
for(let i = 0; i < this.props.numberOfTransmitters; i++) {
transmitters.push(
<tr>
<td><input id={i} ref="axisX" type="text" onChange={this.updateAxisX.bind(this)}/></td>
<td><input id={i} ref="axisY" type="text" onChange={this.updateAxisY.bind(this)}/></td>
<td><input id={i} ref="power" type="text" onChange={this.updateAxisPower.bind(this)}/></td>
</tr>
);
}
return transmitters
}
componentWillMound(){}
render() {
return (
<table>
<thead>
<tr>
<th>Axis X</th>
<th>Axis Y</th>
<th>Power</th>
</tr>
</thead>
<tbody>
{this.generateTransmittersItems()}
</tbody>
</table>
)
}
}
In first row of inputs evrything is okay but if i try to push another value form another row of input's to the same state array (ex. axisX) my console send me this error "this.state.axisX.push is not a function".
What i did wrong and what i have to do to push more values to the same array from input using the same function?
I think the problem isn't related to the react state issue.
When you used the "push" methods, it won't return an array but return the length of the array, and that is the reason why when you use "push" method in second time will get the error "this.state.axisX.push is not a function".
So, if you need to change your state, you can just use "concat" method like this to get a new array as return:
this.setState({
axisX: this.state.axisX.concat([e.target.value])
})
var a = ["Hi", "There"];
var b = a.push("Oh");
console.log(b); // get 3, not an array
console.log(a); // ["Hi", "There", "Oh"]

Categories