So I've created this Table component, intended to be reusable.
But I have this problem where the data didn't fill all the row correctly. It stacked up on first two rows.
Here's my code so far:
Menu.js
export const AddMenu = () => {
const theadData = ["No", "Name", "Category", "Price"];
const tbodyData = [
{
id: 1,
items: [1, "Hamburger", "Fast Food", 150],
},
{
id: 2,
items: [2, "Pizza", "Fast Food", 100],
},
];
return (
<div className="container">
<h1>Add Menu</h1>
<Table theadData={theadData} tbodyData={tbodyData} />
</div>
);
};
Table.js
export const Table = ({
theadData,
tbodyData,
}) => {
return (
<div className="table">
<table>
<thead>
<tr>
{theadData.map((item) => {
return <th title={item}>{item}</th>;
})}
</tr>
</thead>
<tr>
{tbodyData.map((item) => {
return <td key={item}>{item.items}</td>;
})}
</tr>
</tbody>
</table>
</div>
);
};
Thanks btw, hope to get an answer.
Add second map to loop over rows, and then loop over cells, like this:
{
tbodyData.map((row, index) => (
<tr key={index}>
{row.map((item) => {
return <td key={item}>{item}</td>;
})}
</tr>
));
}
Related
My component like this:
import React, { useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import './SizeAndColor.css';
const initialData = {
sizes: ['S', 'M', 'L', 'XL'],
data: [
{
rowId: uuidv4(),
color: 'White',
sizes: [
{ fieldId: uuidv4(), sizeName: 'S', qty: 10 },
{ fieldId: uuidv4(), sizeName: 'M', qty: 20 },
{ fieldId: uuidv4(), sizeName: 'L', qty: 30 },
{ fieldId: uuidv4(), sizeName: 'XL', qty: 40 }
]
},
{
rowId: uuidv4(),
color: 'Gray',
sizes: [
{ fieldId: uuidv4(), sizeName: 'S', qty: 50 },
{ fieldId: uuidv4(), sizeName: 'M', qty: 60 },
{ fieldId: uuidv4(), sizeName: 'L', qty: 70 },
{ fieldId: uuidv4(), sizeName: 'XL', qty: 80 }
]
}
]
};
const initialFieldValue = {
id: 0,
initialData: initialData
};
export default function SizeAndColor() {
const [state, setState] = useState(initialFieldValue);
const onChange = (e, rowId, fieldId) => {
const { name, value } = e.target;
const updatedData = state.initialData.data.map(row => {
if (rowId === row.rowId) {
row.sizes.map(item => {
if (fieldId === item.fieldId) {
if (!isNaN(value)) {
item[name] = value;
}
}
return item;
});
}
return row;
});
setState({ ...state, initialData: { ...initialData, data: updatedData } });
};
return (
<div>
<table>
<thead>
<tr>
<td></td>
{state.initialData.sizes.map(size => (
<td key={uuidv4()}>{size}</td>
))}
</tr>
</thead>
<tbody>
{state.initialData.data.map(item => (
<tr key={uuidv4()}>
<td>{item.color}</td>
{item.sizes.map(size => (
<td key={uuidv4()}>
<input
type="text"
name="qty"
value={size.qty}
onChange={e => {
onChange(e, item.rowId, size.fieldId);
}}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
When I start typing on any input field, It work only first key press. Than again have to click the input field. Than again after first key press, can not type anything.
I tried it with different scenario. If my initialData object doesn't have nested property, It works fine.
how do i solve this issue, please help me.
Issue
You are generating new React keys each render.
<div>
<table>
<thead>
<tr>
<td></td>
{state.initialData.sizes.map(size => (
<td key={uuidv4()}>{size}</td> // <-- here
))}
</tr>
</thead>
<tbody>
{state.initialData.data.map(item => (
<tr key={uuidv4()}> // <-- here
<td>{item.color}</td>
{item.sizes.map(size => (
<td key={uuidv4()}> // <-- here
<input
type="text"
name="qty"
value={size.qty}
onChange={e => {
onChange(e, item.rowId, size.fieldId);
}}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
When you generate a new React key each render cycle it tells React that these are new components, so the previous ones are unmounted and a new component is instantiated. This is why the inputs lose focus.
Solution
Use the ids generated in your data so the React keys are stable, i.e. they don't change from render to render.
<div>
<table>
<thead>
<tr>
<td></td>
{state.initialData.sizes.map((size, index) => (
// use index since size array likely doesn't change
<td key={index}>{size}</td>
))}
</tr>
</thead>
<tbody>
{state.initialData.data.map(item => (
<tr key={item.rowId}> // <-- use row id
<td>{item.color}</td>
{item.sizes.map(size => (
<td key={size.fieldId}> // <-- use field id
<input
type="text"
name="qty"
value={size.qty}
onChange={e => {
onChange(e, item.rowId, size.fieldId);
}}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
The reason for that is that you generate random keys for input on each render, so React thinks it's a new unrelated field that doesn't need to be focused, you need to replace key={uuidv4()} with key={item.rowId} and key={size.fieldId}
Try to remove the returns, the setState should do the work.
I'm new to Javascript so please bear with me. I'm attempting to render a table of data inside a div and I'm unable to do so. Basically I just want to replace the div in the html with the table div we've created.
I'm using react table v7 with webpack. The following code results in this being seen in the browser where the table should be.
Here is my code:
import { useTable } from 'react-table';
const ReactTable = require('react-table');
import React from 'react';
const tableData = [
{someColumn: 1, someOtherColumn: 'A'},
{someColumn: 2, someOtherColumn: 'B'}
]
const columnGenerator = (columnNames) => {
var updatedNames = columnNames.map(c => {
return {
Header: camelToTitle(c),
accessor: c
}
});
return [
{
Header: "Claims Data",
columns: updatedNames
},
]
};
const Table = ({ columns, data }) => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = useTable({
columns,
data
});
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render("Cell")}</td>;
})}
</tr>
);
})}
</tbody>
</table>
);
};
const table = <div>
<Table
columns={columnGenerator(Object.keys(tableData[0]))}
data={tableData}
/>
</div>
document.getElementById('claim-table').append(table);
[object Object] is just what is being rendered by converting that table object into a String. I would start by trying to use ReactDOM to mount that node instead of append:
import ReactDOM from 'react-dom'
...
ReactDOM.render(<Table
columns={columnGenerator(Object.keys(tableData[0]))}
data={tableData}
/>,
document.getElementById('claim-table')
)
I'm using API Values primaryKey to change data represented on Click, but the function doesnt seem to work. and No Errors are thrown. I'm unable to find whats going wrong here.
What I'm trying to do here is - By default the table outputs multiple stocks with multiple columns for each , when clicked on button
it should use the key value of that stock to represent only that single stock with its columns.
Here is my part of the code:
handleClick = (props) => {
return (
<div>
{this.state.data.filter(data => data.includes({props})).map(filteredData => (
<li>
{filteredData}
</li>
))};
</div>
);
}
renderArray = () => {
return (
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Price/ Chng</th>
<th>Mkt Cap</th>
<th>Volume</th>
<th>Turnover</th>
</tr>
</thead>
<tbody>
{this.state.data.map(item => {
return (
<tr key={item.co_S}>
<button onCLick={this.setState = () => this.handleClick(item.co_S)}><td >{item.co_N}</td></button>
<td>{item.price}</td>
<td>{item.p_chng_pc}</td>
<td>{item.Mkt_cap}</td>
<td>{item.volume}</td>
<td>{item.volume * item.price}</td>
</tr>
);
})};
</tbody>
</table>
);
}
render() {
return (
<this.renderArray />
)
}
}
export default StocksHomePage2;
class App extends React.Component {
state = {
data: [
{
co_S: 1,
co_N: 1,
price: 100,
volume: 20,
},
{
co_N: 2,
co_S: 2,
price: 30,
volume: 7,
},
],
};
handleClick = (props) => {
this.setState({
data: this.state.data.filter((item) => item.co_S === props),
});
};
renderArray = () => {
return (
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Price/ Chng</th>
<th>Mkt Cap</th>
<th>Volume</th>
<th>Turnover</th>
</tr>
</thead>
<tbody>
{this.state.data.map((item) => {
return (
<tr key={item.co_S}>
<button onClick={() => this.handleClick(item.co_S)}>
<td>{item.co_N}</td>
</button>
<td>{item.price}</td>
<td>{item.p_chng_pc}</td>
<td>{item.Mkt_cap}</td>
<td>{item.volume}</td>
<td>{item.volume * item.price}</td>
</tr>
);
})}
</tbody>
</table>
);
};
render() {
return this.renderArray();
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
so I have two buttons in my react App.js and when clicked I want my current state(list) to change to descending order according to which button i press(order by date or order by upvotes). My articles.js have the code that display the list of articles. But I'm having a hard time showing the list sorted after clicking the button tag found on my App.js which is the parent component.
import React, { useState } from 'react';
function Articles({articles}) {
const [list, setList] = useState(articles)
return (
<div className="card w-50 mx-auto">
<table>
<thead>
<tr>
<th>Title</th>
<th>Upvotes</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{list.map((a, i) =>
<tr data-testid="article" key={i}>
<td data-testid="article-title">{a.title}</td>
<td data-testid="article-upvotes">{a.upvotes}</td>
<td data-testid="article-date">{a.date}</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
export default Articles;
import React from 'react';
import './App.css';
import 'h8k-components';
import Articles from './components/Articles';
const title = "Sorting Articles";
function App({articles}) {
//set article to state then pass
const handleUpvotes = () => {
articles.sort((a, b) => a.upvotes - b.upvotes).reverse()
console.log(articles)
}
const handleDates = () => {
return
}
return (
<div className="App">
<h8k-navbar header={title}></h8k-navbar>
<div className="layout-row align-items-center justify-content-center my-20 navigation">
<label className="form-hint mb-0 text-uppercase font-weight-light">Sort By</label>
<button data-testid="most-upvoted-link" className="small" onClick={handleUpvotes}>Most Upvoted</button>
<button data-testid="most-recent-link" className="small" onClick={handleDates}>Most Recent</button>
</div>
<Articles articles={articles}/>
</div>
);
}
export default App;
The useState should be in the App
const [list, setList] = useState(articles)
//set article to state then pass
const handleUpvotes = () => {
articles.sort((a, b) => a.upvotes - b.upvotes).reverse()
setList(articles)
}
You should use the Effect Hook (https://reactjs.org/docs/hooks-effect.html).
useEffect(() => {
// articles was changed
}, [articles])
the problem that you are facing is that a misunderstanding of the React reactivity model, now lets take a look at this line
articles.sort((a, b) => a.upvotes - b.upvotes).reverse()
here you are successfully updating the array, but think about it. if React updated the UI whenever a variables inside the component updates that would be ineffective and problematic.
so in order to notify React about what has changed and it needs to update the UI, whenever you change a variable and you need the UI to update, you use useState from react.
and another point is that in your Article component you are expecting props, and calling useState at the time.
so moving the useState into the App component dose the work
const [list, setList] = useState(articles)
const handleUpvotes = () => {
articles.sort((a, b) => a.upvotes - b.upvotes).reverse()
setList(articles)
}
It is not clear where articles come from and if they need to be used in multiple components so I'll put them in context, that way you can use it anywhere in your application.
const ArticleContext = React.createContext();
const ArticleProvider = ({ children }) => {
const [articles, setArticles] = React.useState([
{ title: '1', upvotes: 1, date: 1 },
{ title: '3', upvotes: 3, date: 3 },
{ title: '2', upvotes: 2, date: 2 },
{ title: '4', upvotes: 4, date: 4 },
]);
const sortDirection = React.useRef(-1);
const sortByUpvotes = React.useCallback(() => {
//toggle sort direction
sortDirection.current = sortDirection.current * -1;
setArticles((articles) =>
[...articles].sort(
(a, b) =>
(a.upvotes - b.upvotes) * sortDirection.current
)
);
}, [setArticles]);
return (
<ArticleContext.Provider
value={{
articles,
sortByUpvotes,
}}
>
{children}
</ArticleContext.Provider>
);
};
function Articles() {
const { articles } = React.useContext(ArticleContext);
return (
<div className="card w-50 mx-auto">
<table>
<thead>
<tr>
<th>Title</th>
<th>Upvotes</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{articles.map((a, i) => (
<tr data-testid="article" key={i}>
<td data-testid="article-title">{a.title}</td>
<td data-testid="article-upvotes">
{a.upvotes}
</td>
<td data-testid="article-date">{a.date}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
function App() {
const { sortByUpvotes } = React.useContext(
ArticleContext
);
return (
<div className="App">
<div className="layout-row align-items-center justify-content-center my-20 navigation">
<label className="form-hint mb-0 text-uppercase font-weight-light">
Sort By
</label>
<button
data-testid="most-upvoted-link"
className="small"
onClick={sortByUpvotes}
>
Most Upvoted
</button>
</div>
{/* no need to pass articles, they are in context */}
<Articles />
</div>
);
}
ReactDOM.render(
<ArticleProvider>
<App />
</ArticleProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The next example shows how to sort using multiple fields:
const ArticleContext = React.createContext();
const ArticleProvider = ({ children }) => {
const [articles, setArticles] = React.useState([
{ title: '1', upvotes: 1, date: 3 },
{ title: '3', upvotes: 3, date: 3 },
{ title: '2', upvotes: 2, date: 4 },
{ title: '4', upvotes: 4, date: 2 },
]);
const sortDirection = React.useRef([-1, -1]);
const sortPriority = React.useRef([0, 1]);
const sortFunctions = React.useMemo(
() => [
(a, b) =>
(a.upvotes - b.upvotes) * sortDirection.current[0],
(a, b) =>
(a.date - b.date) * sortDirection.current[1],
],
[]
);
const sort = React.useCallback(() => {
setArticles((articles) =>
[...articles].sort((a, b) =>
sortPriority.current.reduce(
(result, fnIndex) =>
result === 0
? sortFunctions[fnIndex](a, b)
: result,
0
)
)
);
}, [sortFunctions]);
const setDirectionAndPriority = (num) => {
if (sortPriority.current[0] === num) {
sortDirection.current[num] =
sortDirection.current[num] * -1;
}
sortPriority.current = [
num,
...sortPriority.current.filter((n) => n !== num),
];
};
const sortByUpvotes = () => {
setDirectionAndPriority(0);
sort();
};
const sortByDate = () => {
setDirectionAndPriority(1);
sort();
};
return (
<ArticleContext.Provider
value={{
articles,
sortByUpvotes,
sortByDate,
}}
>
{children}
</ArticleContext.Provider>
);
};
function Articles() {
const { articles } = React.useContext(ArticleContext);
return (
<div className="card w-50 mx-auto">
<table>
<thead>
<tr>
<th>Title</th>
<th>Upvotes</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{articles.map((a, i) => (
<tr data-testid="article" key={i}>
<td data-testid="article-title">{a.title}</td>
<td data-testid="article-upvotes">
{a.upvotes}
</td>
<td data-testid="article-date">{a.date}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
function App() {
const { sortByUpvotes, sortByDate } = React.useContext(
ArticleContext
);
return (
<div className="App">
<div className="layout-row align-items-center justify-content-center my-20 navigation">
<label className="form-hint mb-0 text-uppercase font-weight-light">
Sort By
</label>
<button
data-testid="most-upvoted-link"
className="small"
onClick={sortByUpvotes}
>
Most Upvoted
</button>
<button
data-testid="most-recent-link"
className="small"
onClick={sortByDate}
>
Most Recent
</button>
</div>
{/* no need to pass articles, they are in context */}
<Articles />
</div>
);
}
ReactDOM.render(
<ArticleProvider>
<App />
</ArticleProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
import React, { Component } from 'react'
import * as ReactBootStrap from 'react-bootstrap'
import { Table } from 'react-bootstrap'
import firebase from '../fire'
import '../App.css'
import Foot from './Foot'
class Appointment extends Component {
state = {
data: []
}
componentDidMount() {
firebase.database().ref("appoinment").once("value").then(snapShot => {
snapShot.forEach(item => {
this.state.data.push({
id: item.key,
name: item.val().name,
age: item.val().age,
gender: item.val().gender,
Description: item.val().Description,
date: item.val().Appointdate
});
})
})
}
render() {
return (
<div className='cardback'>
<div>
<br></br>
{console.log(this.state)}
<br></br>
<h2 style={{ textAlign: 'center', fontSize: '30px' }}>Today's Appointment</h2>
<br></br>
<br></br>
<Table striped bordered hover variant="dark" style={{ width: "1200px", margin: 'auto' }}>
<thead>
<tr>
<td>id</td>
<td>name</td>
<td>age</td>
<td>gender</td>
<td>Description</td>
<td>date</td>
<td>Status</td>
</tr>
</thead>
<tbody>
**{
this.state.data.map((item) =>
<tr>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.age}</td>
<td>{item.gender}</td>
<td>{item.Description}</td>
<td></td>
</tr>
)
}**
</tbody>
</Table>
<br></br>
<br></br>
</div>
<Foot></Foot>
</div>
)
}
}
export default Appointment;
This is above is code
I want to fetch data from the firebase to react-js application. I am able to get whole data at the console but not able to iterate it into table form. The is as below. In which I fetch data from firebase and pushed into an array data. So basically data is an array of objects. But I am not able to iterate i
You are mutating the state directly which will not cause any re-render. Don't do this.state.data.push({id:item.key,
Use this.setState
componentDidMount() {
firebase
.database()
.ref("appoinment")
.once("value")
.then((snapShot) => {
let updatedData = [];
snapShot.forEach((item) => {
updatedData.push({
id: item.key,
name: item.val().name,
age: item.val().age,
gender: item.val().gender,
Description: item.val().Description,
date: item.val().Appointdate,
});
});
this.setState({ data: updatedData });
});
}