I receive an array like this from backend:
[
{
id: 0,
name: "John",
language: "Enlgish"
},
{
id: 1,
name: "Chris",
language: "Spanish"
},
{
id: 2,
name: "Bastian",
language: "German"
}
]
So I display the languages from this array in a table, and to do that I map through them.
I don't want to show the first language on the first object of this array
Parent.js
const [language, setLanguage] = useState ([])
useEffect(() => {
axios
.get('api').then((res) => {setLanguage(response.data.languages)})
}, [])
Child.js
return(
{language.map((lang, i) => {
return (
<tr key={"item-" + i}>
<td>
<div>
<input
type="text"
value={
lang.language
? lang.language.shift()
: lang.language
}
</div>
</td>
</tr>
))}
)
So what I have tried by far is the shift method which removes the first item of an array, but it didn't work.
This error happened :TypeError: lang.language.shift is not a function
How can I fix this?
Use the index
{language.map((lang, i) => {
(i > 0) && (
return (
......
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 });
};
const keys = data.getUser.keys;
console.log(keys)
for(let i = 0, l = keys.length; i < l; i++) {
var keyNickname = keys[i]['nickname'];
var keyExchange = keys[i]['exchange'];
var keyLivenet = keys[i]['livenet'];
console.log(keyNickname)
const createRow = (
rowId: number,
keyNickname: string,
keyExchange: string,
keyLivenet: string,
) => ({
cells: [
{ content: `${keyNickname}`, key: `${rowId}` },
{ content: `${keyExchange}`, key: `${rowId}` },
{ content: `${keyLivenet}`, key: `${rowId}` },
],
});
var rows = keys.map((a: number) =>
createRow(a, keys[i]['nickname'], keys[i]['exchange'], keys[i]['livenet'])
);
}
I'm so close yet so far. I'm just unsure how to properly map the data to the three rows!
Any assistance would be awesome, I'm trying to learn the hard way (and I have been.) though, I would love to solve this so I can move onto create other tables throughout the application.
EDIT: full page as requested:
const headSet = ['Nickname', 'Exchange', 'Livenet'];
const head = {
cells: headSet.map(headSet => ({
key: headSet,
content: headSet,
shouldTruncate: true,
isSortable: true,
})),
};
const keys = data.getUser.keys;
console.log(keys)
for(let i = 0, l = keys.length; i < l; i++) {
var keyNickname = keys[i]['nickname'];
var keyExchange = keys[i]['exchange'];
var keyLivenet = keys[i]['livenet'];
console.log(keyNickname)
const createRow = (
rowId: number,
keyNickname: string,
keyExchange: string,
keyLivenet: string,
) => ({
cells: [
{ content: `${keyNickname}`, key: `${rowId}` },
{ content: `${keyExchange}`, key: `${rowId}` },
{ content: `${keyLivenet}`, key: `${rowId}` },
],
});
var rows = keys.map((a: number) =>
createRow(a, keys[i]['nickname'], keys[i]['exchange'], keys[i]['livenet'])
);
}
return (
<PageLayout>
<TopNavigation height={60} isFixed={true}>
<TopNav />
</TopNavigation>
<Content>
<LeftSidebar width={240} isFixed={true}>
<SideBar />
</LeftSidebar>
<Main>
<div
style={{
marginLeft: 40,
marginRight: 40,
}}
>
<PageHeader
actions={actionsContent}
>
Connections
</PageHeader>
<DynamicTable
head={head}
rows={rows}
defaultPage={1}
loadingSpinnerSize="large"
isLoading={false}
isFixedSize
defaultSortKey="term"
defaultSortOrder="ASC"
onSort={() => console.log('onSort')}
onSetPage={() => console.log('onSetPage')}
rowsPerPage={!keys.length ? 10 : undefined}
/>
</div>
</Main>
</Content>
</PageLayout>
);
}
export default Keys;
You're passing bad arguments to your map function. A cleaner way would be this:
var rows = keys.map((key: any, i: number) =>
createRow(i, key.nickname, key.exchange, key.livenet)
);
Array.prototype.map takes a function with two arguments. The first is each element of the array as it is iterated over. The second is the index of the element in the array.
In your code, you're A) missing the first argument, and B) mistaking 'a' for 'i' in your function when dealing with the index.
Can you please provide the full source code for this page? This is tagged as react, but you aren't including any JSX, so I'm not sure if you meant to. I put an example below, but I have no idea of the structure of the data you imported.
const keys = data.getUser.keys
function TodosList(keys){
const renderRow = () => {
return keys.map(function(key, i){
return(
<tr>
<td>{key.keyNickname}</td>
<td>{key.keyExchange}</td>
<td>{key.keyLivenet}</td>
</tr>
)
})
}
return(
<div><h3>Key List?</h3>
<table className="table table-striped" style={{ marginTop: 20 }}>
<thead>
<tr>
<th>Heading 1</th>
<th>Heading 2</th>
<th>Heading 3</th>
</tr>
</thead>
<tbody>
{renderRow()}
</tbody>
</table>
</div>
)
}
export default TodosList
I'm building a terribly flawed e-commerce application using React just for fun and I'm trying to figure out how to set state on a certain object once it's pushed into an array.
I have a cart array where I push the items that I added from my initial items array which holds all my products.
The iffy thing is that I have stock on products. So let's say my chocolate product has 5 in stock and every time I push that the chocolate object, it piles on and adds the same item in the cart as so:
I want to be pushing the chocolates object to the cart array but I don't want to render a duplicate if it's already there. Instead I want to achieve something where the chocolate object is added but the quantity of it is changed accordingly every time it's added. It would look something like this:
How can I achieve something like this? Maybe a check to see if that object is already added to the cart array and if it is then instead of rendering a duplicate, just push the values and update a quantity of that item?
Been stuck for hours and would greatly appreciate some hints.
class App extends Component {
state = {
cart: [],
items: [
{ id: uuid(), name: 'chocolate', price: 10, remaining: 5 },
{ id: uuid(), name: 'strawberry', price: 50, remaining: 10 },
{ id: uuid(), name: 'banana', price: 43, remaining: 20 }
],
total: 0,
addToCartMessage: false,
removeFromCartMessage: false,
searchTerm: ''
}
addItem = item => {
const { cart, items, total } = this.state
cart.push({ id: item.id, name: item.name, price: item.price })
const remaining = item.remaining--
const totalPrice = cart.reduce((a, b) => a + b.price, 0)
this.setState({
total: totalPrice,
cart,
addToCartMessage: true,
...items, remaining
})
}
removeItem = cartItems => {
const { items, cart, total } = this.state
const removeItem = cart.filter(item => item.id !== cartItems.id)
const itemId = items.find(item => item.name === cartItems.name).remaining++
this.setState({
removeFromCartMessage: true,
total: total - cartItems.price,
cart: removeItem,
...items, remaining: itemId
})
}
render() {
const { cart, items, total, addToCartMessage, removeFromCartMessage } =
this.state
if (addToCartMessage || removeFromCartMessage) {
setTimeout(() => {
this.setState({
addToCartMessage: false,
removeFromCartMessage: false
})
}, 1000)
}
const filteredItems = items.filter(item =>
item.name.includes(this.state.searchTerm))
return (
<div className="App">
{cart.length === 0 ? <h3>No items in cart</h3> : (
<div>
<h1>Cart:</h1>
{cart.map(items => (
<div key={items.id}>
<h1>{items.name} x 3</h1>
<p>${items.price}</p>
<button onClick={() => this.removeItem(items)}>Remove From Cart</button>
</div>
))}
</div>
)}
<hr />
<input
type="text"
placeholder="Search for an item..."
onChange={e => this.setState({ searchTerm: e.target.value })}
value={this.state.searchTerm}
/>
{filteredItems.map(item => (
<div key={item.id}>
<h1>{item.name}</h1>
<p>Price: ${item.price}</p>
{item.remaining === 0 ? <p>Sold Out</p> : (
<div>
<p>Remaining: {item.remaining}</p>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
{ total !== 0 ? <h1>Total ${total}</h1> : <h1>Total $0</h1> }
{ addToCartMessage && <h1>Item successfully added!</h1> }
{ removeFromCartMessage && <h1>Item successfully removed!</h1> }
</div>
)
}
}
export default App
Store your products in a regular object by id.
1: {
id: 1,
name: 'chocolate'
}
Store your cart as an array of IDs.
[1, 1, 1]
In your component, group IDs cart array by ID to get the count, and look up cart object by ID to get its data.
Computed data should be computed, not stored.
Here's some completely untested, unlinted, code showing the calculations done in the render function:
class App extends Component {
state = {
cart: [],
items: [
{ id: uuid(), name: 'chocolate', price: 10, available: 5 },
{ id: uuid(), name: 'strawberry', price: 50, available: 10 },
{ id: uuid(), name: 'banana', price: 43, available: 20 }
// Convert to an object of { id: { id, name, price } }
].reduce((memo, item) => ({
...memo,
[item.id]: item
}), {}),
}
addItem = id => {
const { cart, } = this.state
this.setState({
cart: [ ...cart, id ]
})
}
removeItem = removeId => {
const { cart, } = this.state
this.setState({
cart: cart.filter(({ id }) => id !== removeId)
})
}
render() {
const { cart, items, total, addToCartMessage, removeFromCartMessage } = this.state
// Given an array of item IDs in our cart, group them into an object
// with the total count and price for each item, and overall count
const accumulatedItems = items.reduce((memo, item) => {
const { id, price } = item;
const { count, price, } = memo[id] || {};
return {
...memo,
cartTotal: memo.cartTotal + price,
[id]: {
count: (count || 0) + 1,
total: (price || 0) + price,
}
};
// Starting object to reduce
}, {
cartTotal: 0,
});
return (
<div className="App">
{cart.length === 0 ? <h3>No items in cart</h3> : (
<div>
<h1>Cart:</h1>
{Object.keys(accumulatedItems).sort().map(id => (
<div key={id}>
<h1>{items[id].name} x {accumulatedItems[id].total}</h1>
<p>${accumulatedItems[id].total}</p>
<button onClick={() => this.removeItem(id)}>Remove From Cart</button>
</div>
))}
</div>
)}
</div>
);
}
}
Juggling computed data, and mutating state like remaining, adds significant logic complexity to your app. You should only start worrying about trying to store/cache/memoize computed state once there are performance issues. Calculating totals in the render function is a fine first pass.
In React in the wild, there are solutions like reselect, which don't require redux technically. They're just caching functions that take in a given state, and produce a computed output, and only re-calculate the output if the input changes.
Maybe you can do it how you think and explain in your question. There are multiple ways of doing this and everyone do it how they like it or how easy they do and maintain it.
Instead of inserting the item itself maybe you can hold an object for each item in your array with item's unique id. That object also could hold quantity. Then you can generate card info via this unique id.
Example:
cart: [
{ id: uniqeId, quantiy: 1 },
{ id: uniqeId, quantiy: 6 },
]
After adding an item to card, you can go and just alter the related object's quantity. But for this you have to find the item in this array, then alter the quantity as you guess.
You can have item ids in your cart object (not array this time) as an array but this time you separate quantity and hold it as an object by item ids. So, after adding the item to cart's id array list, you also go and alter quantity of item's object. With this method you don't have to struggle finding anything but you need to alter two piece of information.
Like:
cart: {
ids: [ uniqueId, uniqueId ],
quantity: { uniqueId: 1, uniqueId: 6 }
}
Or you can do how you describe, just add the item but before doing this check if the item is already there. For example filtering by id. But, with this technique there might be some glitches. When you add items like this, for example with price, remaining etc, you also have to maintain your cart state with your item state. For instance what will happen when you want to change an item's price? Or what if there is another way (somehow) altering the remaining other then adding items into cart? But, if you play only with id's you can extract those information from your single state: items.
But I'm also a learner, maybe there are way better methods apart from those. I haven't written a cart yet, but if I did it I would use the second method maybe.
By the way, do not use push to alter your state. This mutates your state and it is not advisable. Use something like concat or spread operator to create a new state.
item = { foo: bar, fizz: buzz }
// now setting state
cart: [ ...this.state.cart, item ]
And try to use a callback function in setState (since it is an async operation) if your state change depends on the previous state.
this.setState( prevState => ( {
cart: [ ...prevState.cart, item ],
} ) )
Using #devserkan suggestion of restructuring the cart state and #Andy Ray's suggestion of restructuring the items state, I set up my state to look like this:
state = {
items: {
1: {
id: 1, name: 'chocolate', price: 10, available: 5
},
2: {
id: 2, name: 'strawberry', price: 10, available: 10
},
3: {
id: 3, name: 'banana', price: 10, available: 20
}
}
cart: {
ids: [],
quantity: { 1: 0, 2: 0, 3: 0 }
}
}
I then went and rendered out the items and added an onClick function (addItem) to handle some setState calls:
render() {
const { cart, items } = this.state
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>{item.price}</h2>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
))}
In my addItem function, I went ahead and set the state of cart so that I push the item id, and update the quantity on that id as well:
addItem = item => {
const { cart, items } = this.state
this.setState({
cart: {
...cart,
// Push item id to ids array inside cart state
ids: [...cart.ids, item.id],
quantity: {
...cart.quantity,
// Update quantity of the specific id pushed by 1
[item.id]: cart.quantity[item.id] + 1
}
}
})
}
Finally I had to render the cart section: I did so by checking to see if the cart.ids array wasn't empty and made another check to only render the item that has a quantity greater than 0. If we didn't make that check, every time we push an item, it will add all 3 at once and we only want that specific item to show.
{cart.ids.length !== 0 ? Object.keys(items).map(id => (
<div key={id}>
// Check to see if quantity for that item is > 0
{cart.quantity[id] > 0 && (
<h1>{items[id].name} x {cart.quantity[id]}</h1>
)}
</div>
)) : <h1>No Items In Your Cart</h1>}
Full Code (Without Price / Remaining)
export default class App extends Component {
state = {
cart: {
ids: [],
quantity: {
1: 0,
2: 0,
3: 0
}
},
items: {
1: {
id: 1, name: 'chocolate', price: 10, available: 5
},
2: {
id: 2, name: 'strawberry', price: 10, available: 10
},
3: {
id: 3, name: 'banana', price: 10, available: 20
}
}
}
addItem = item => {
const { cart, items } = this.state
this.setState({
cart: {
...cart,
ids: [...cart.ids, item.id],
quantity: {
...cart.quantity,
[item.id]: cart.quantity[item.id] + 1
}
}
})
}
removeItem = removeId => {
const { cart } = this.state
this.setState({
cart: cart.filter(({ id }) => id !== removeId)
})
}
render() {
const { cart, items, total, addToCartMessage, removeFromCartMessage } =
this.state
return (
<div className="App">
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>{item.price}</h2>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
))}
<hr style={{'marginTop': '200px'}} />
<h1>Cart</h1>
{cart.ids.length !== 0 ? Object.keys(items).map(id => (
<div key={id}>
{cart.quantity[id] > 0 && (
<h1>{items[id].name} x {cart.quantity[id]}</h1>
)}
</div>
)) : <h1>No Items In Your Cart</h1>}
</div>
)
}
}
Big thanks to #Andy Ray and #devserkan for the suggestions.
I'd like to sort table items (alphabetical) by clicking on table header. I've tried to do it myself, but it works really strange, only clicking on second header (priority) works... And when I click on first and third header, it sorts table items in order how they were put in there.
I use orderBy from lodash.
Here is my code, the slice of the full class.
const header = [
{name: "Task Name", id: "taskName"},
{name: "Priority", id: "priority"},
{name: "Done", id: "done"},
];
<TableHead>
<TableRow>
{header.map((el, i) => (
<TableCell key={i}>
<div
style={{
display: 'flex',
alignItems: 'center'
}}
onClick={() => this.props.handleSort(el.id)}
>
{el.name}
{
this.props.columnToSort === el.id
? (this.props.sortDirection === 'asc'
? <UpArrow/>
: <DownArrow/>
)
: null
}
</div>
</TableCell>
))}
<TableCell/>
</TableRow>
</TableHead>
And logics in different class, "connected" by props.
const invertDirection = {
asc: "desc",
desc: "asc",
};
class...
state = {
columnToSort: '',
sortDirection: 'desc',
};
handleSort = (columnName) => {
this.setState({
columnToSort: columnName,
sortDirection:
this.state.columnToSort === columnName
? invertDirection[this.state.sortDirection]
: 'asc',
});
};
props
tableData={orderBy(
this.state.tableData,
this.state.columnToSort,
this.state.sortDirection
)}
handleSort = {this.handleSort}
columnToSort = {this.state.columnToSort}
sortDirection = {this.state.sortDirection}
I know it may be hard to read, because I've got many components, but pasted only things I use to do a sort.
Can you tell me why when clicking on second table header priority, sorting works, and when clicking on other headers it don't?
If you have any better ideas for sorting, please let me know.
I'm hopping i understand your goal here, you are trying to sort the data via a click on the table's headers and toggle it to sort it in ascending or descending manner.
If this is correct i would take a simpler approach.
Sorting by dynamic key
You can create a Th component of your own that will take an onClick prop and an id prop where the id is the name of the object's key.
When the Th is clicked it will invoke the handler and will pass the id (the object's key) to the handler.
This way you can sort on the key that got passed by the child.
Ascending Or Descending
We only have 2 options for sorting - Ascending or Descending. This means we can use a Boolean instead of a string (that will simplify our logic a bit).
So after each click on a given Th we will set a new Boolean object in our state when the key being the id of the Th and we will flip it's value.
This way we can conditionally sort by the given key either in an ascending or descending way.
Here is a small running example:
const data = [
{ name: 'John', age: 32 },
{ name: 'Mike', age: 27 },
{ name: 'Jane', age: 31 },
{ name: 'Criss', age: 25 },
{ name: 'Tom', age: 18 },
]
class Th extends React.Component {
handleClick = () => {
const { onClick, id } = this.props;
onClick(id);
}
render() {
const { value } = this.props;
return (
<th onClick={this.handleClick}>{value}</th>
)
}
}
class App extends React.Component {
state = {
users: data
}
handleSort = (id) => {
this.setState(prev => {
return {
[id]: !prev[id],
users: prev.users.sort((a, b) => prev[id] ? a[id] < b[id] : a[id] > b[id] )
}
});
}
render() {
const { users } = this.state;
return (
<table>
<thead>
<tr>
<Th onClick={this.handleSort} id="name" value="Name" />
<Th onClick={this.handleSort} id="age" value="Age" />
</tr>
</thead>
<tbody>
{
users.map(user => (
<tr>
<td>{user.name}</td>
<td>{user.age}</td>
</tr>
))
}
</tbody>
</table>
);
}
}
ReactDOM.render(<App />, document.getElementById('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>