I've got my parent component iterating through an array and passing each "row" from mongo to the child component.
<tbody>
{this.state.claims.map ( claim => <ClaimRow claim= { claim } /> )}
</tbody>
The data being passed looks like this:
{"_id":"5b0d5b7f035a00f06003e6b8","claimID":"123456","claimDate":"2018-05-14T00:00:00.000Z","carrier":"BCBS NJ"}
I'm attempting to access all of the fields inside "claim" but I can't figure out how to properly access the field. Since there isn't a state, I am using a pure function. I just listed the fields below since I'm not able to successfully figure this out.
const ClaimRow = ( {claim} =this.props ) => (
<div className="inline fields">
<Form.Field>
<tr>
<td> {JSON.stringify (claim)} </td>
<td>{claimID}</td>
<td>{carrier}</td>
</tr>
</Form.Field>
</div>
);
ClaimRow.propTypes = {
claim: PropTypes.string.isRequired
};
export default ClaimRow;
It is not a proper way to provide form and div tags inside . If you want to assign class name, you can assign it to the td, or give div inside . Now without these you can properly loop thru the data in the following way,
const ClaimRow = (props) => {
let claim = props.claim;
return (
<tr>
<td>{JSON.stringify (claim)} </td>
<td>{claim.claimID}</td>
<td>{claim.carrier}</td>
</tr>
);
}
export default ClaimRow;
You should access through this.props.claim,
const ClaimRow = ({claim}) => (
<tr>
<td>{JSON.stringify(claim)}</td>
<td>{claim.claimID}</td>
<td>{claim.carrier}</td>
</tr>
export default ClaimRow;
you can even say const { claim } = this.props, so you dont need to type this.props every time
Thx Rohit, the props log helped solve the problem. Invoking the component twice was causing odd behavior and highlighting it resolved it quickly.
Related
So, I'm trying to create a simple program in react. The state is an array of objects. It prints these objects into a table. Standard table construction (, , ) etc. works fine for the initial render, but when I try to re-render I get bizarre errors I'm trying to adapt to. So, this is my code so far.
import React from "react";
import ReactDOM from "react-dom";
import { useState } from "react";
import "./style.css";
function Trains() {
const [trainsList, setTrainsList] = useState([
{ name: "Thomas", destination: "Boston" },
{ name: "Duncan", destination: "New York" },
]);
return (
<div>
<table>
<thead>name</thead>
<thead>destination</thead>
{trainsList.map((train) => (
<tbody key={train.name}>
<td>{train.name}</td>
<td>{train.destination}</td>
</tbody>
))}
</table>
<br />
Train Name: <input type="text" id="trainName" />
<br />
Destination: <input type="text" id="trainDest" />
<br />
<button
onClick={() =>
setTrainsList((prev) =>
prev.push({ name: "Dennis", destination: "Denville" })
)
}
>
Submit
</button>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Trains />);
But even changing the table lingo like that still isn't enough. I get these bizarre errors:
bundle.js:7294 Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <thead>.
at thead
at table
at div
at Trains (http://localhost:3000/static/js/bundle.js:32:86)
And
bundle.js:7294 Warning: validateDOMNesting(...): <td> cannot appear as a child of <tbody>.
at td
at tbody
at table
at div
at Trains (http://localhost:3000/static/js/bundle.js:32:86)
How on Earth can I create a table or use "thead" to any effect if I can't even put any text in the headings or put tds in my table rows? How the heck do I make tables in react?
Table Rows
You've probably intended to make your table look something like that:
<table>
<thead>
<tr>
<th>name</th>
<th>destination</th>
</tr>
</thead>
<tbody>
{trainsList.map((train) => (
<tr key={index}>
<td>{train.name}</td>
<td>{train.destination}</td>
</tr>
))}
</tbody>
</table>
Note the <tr> elements to create a table rows both in the header and in the body.
Key in a map
Also, note the key attribute in the <tr> element. It is not a good practice to use an index of an array as a key, although it is much better that using a name that can be repeated.
Fix setState
Finally, the function to update state after clicking a button can be rewritten to that:
<button
onClick={() =>
setTrainsList((prev) => {
return [
...prev,
{ name: "Dennis", destination: "Denville" },
];
})
}
>
Submit
</button>
The error is in the setTrainsList method defined in the Submit button. The push method returns the new length of the array, but does not change the original array. Instead, you must use the concat method or the spread syntax to add a new element to an array in React:
<button onClick={() =>
setTrainsList((prev) => [...prev, { name: "Dennis", destination: "your butt" }])
}
>
Submit
</button>
This should resolve the error and add a new element to the train list when you click the button.
I am using bootstrap-table and trying to display a table with data from a firebase realtime database.
The contents of the database render correctly, however I am also seeing a "No matching records found" message in my table.
Using the search and sort functionality also clear the table completely and a refresh is needed to restore the data.
import React, { useState, useEffect } from "react";
import firebaseDb from "../firebase";
const StarTrekTable = () => {
var [contactObjects, setObjects] = useState({});
useEffect(() => {
firebaseDb.child("unwatched").on("value", (snapshot) => {
if (snapshot.val() != null)
setObjects({
...snapshot.val(),
});
});
}, []);
return (
<>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display -4 text-center">Star trek series</h1>
</div>
</div>
<div>
<div className="col-md-12">
<table data-toggle="table" data-pagination="true" data-search="true">
<thead className="thread-light">
<tr>
<th>Title</th>
<th>Series</th>
<th>Season</th>
<th>Episode</th>
<th>Stardate</th>
<th>Air Date</th>
</tr>
</thead>
<tbody>
{Object.keys(contactObjects).map((id) => {
return (
<tr>
<td>{contactObjects[id].title}</td>
<td>{contactObjects[id].series}</td>
<td>{contactObjects[id].season}</td>
<td>{contactObjects[id].episode}</td>
<td>{contactObjects[id].stardate}</td>
<td>{contactObjects[id].airdate}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</>
);
};
export default StarTrekTable;
Any help would be very much appreciated.
Table shows both records as expected and "No matching records found"
Image of my Firebase realtime database setup
On the first render of your component, your contactsObject is {}, which leads to <tbody> being empty on the first render. Somewhere else in your code, probably somewhere in bootstrap, the "No matching records found" is added into the source. Once your listener has found the unwatched episodes, downloaded, parsed, and added them to the <tbody> react then removes the entries it has added (none) and then inserts the new entries. Because "No matching records found" was added outside of React, it doesn't know that it is there and isn't removed. Rather than let whatever other code is adding this row in, define it yourself when you are still loading data or the table is empty.
If you aren't already using react-bootstrap, you should use it so that the original JavaScript bundled with Bootstrap doesn't fight with React for control like you've seen here.
Note: When rendering arrays of data in React, make sure to specify a key for each entry so that React can efficiently manipulate the entries on new renders.
Cleaning up some other things too, this gives:
import React, { useState, useEffect } from "react";
import firebaseDb from "../firebase";
const StarTrekTable = () => {
// - renamed variables to reflect content
// - used array instead of object
// - used const instead of var
const [episodeData, setEpisodeData] = useState([]);
const [episodesLoaded, setEpisodesLoaded] = useState(false);
useEffect(() => {
// store Reference for attaching and detaching listeners
const unwatchedRef = firebaseDb.child("unwatched");
// because you use a `.on` listener, don't forget to store
// the listener so we can detach it
const listener = unwatchedRef
.on("value", (snapshot) => {
if (!snapshot.exists()) {
// no data to show and/or unwatched is empty
// update state variables
setEpisodeData([]);
setEpisodesLoaded(true);
return;
}
// Use forEach to maintain the order of the query, using `...snapshot.val()`
// for the whole result will often lead to unexpected results.
const freshEpisodeData = [];
snapshot.forEach((childSnapshot) => {
freshEpisodeData.push({ ...childSnapshot.val(), key: childSnapshot.key });
});
// update state variables
setEpisodeData(freshEpisodeData);
setEpisodesLoaded(true);
});
// when component is detached, remove the listener
return () => unwatchedRef.off("value", listener);
}, []);
return (
<>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display -4 text-center">Star trek series</h1>
</div>
</div>
<div>
<div className="col-md-12">
<table data-toggle="table" data-pagination="true" data-search="true">
<thead className="thread-light">
<tr>
<th>Title</th>
<th>Series</th>
<th>Season</th>
<th>Episode</th>
<th>Stardate</th>
<th>Air Date</th>
</tr>
</thead>
<tbody>
{ !episodesLoaded
? (
<tr key="data-loading">
<td colspan="6"><i>Loading...</i></td>
</tr>
)
: (episodeData.length === 0
? (
<tr key="data-empty">
<td colspan="6">No unwatched episodes available. You're all caught up!</td>
</tr>
)
: episodeData.map(({ key, title, series, season, episode, stardate, airdate }) => (
<tr key={key}>
<td>{title}</td>
<td>{series}</td>
<td>{season}</td>
<td>{episode}</td>
<td>{stardate}</td>
<td>{airdate}</td>
</tr>
)))
}
</tbody>
</table>
</div>
</div>
</>
);
};
export default StarTrekTable;
Note: For static arrays that are unlikely to change, your database structure is fine, but I would strongly advise against using numerical keys for your data (0, 2, etc.) and instead use something else like (S01E01-02, S01E03, etc.) or Firebase Push IDs. Take at this (yet still valid) blog post for the reasoning.
I'm building a catalogue of restaurants in the country. The API returns an array of objects, each one consisting of fields such as restaurantName, restaurantLocation, restaurantPriceRange.
I want to create a filtering component, which will reduce the array of restaurants to a subset containing only those that match the search criteria. I also need to pass the filter values via the URL, for which I believe I should use a router.
I was thinking about storing the initial data set with useState hook and then using useReducer to store active filter selection and a subset of the filtered restaurant collection with only those matching the search criteria, but I feel like this is a clumsy solution, as effectively I would be duplicating a subset of the entire restaurant collection.
However, if I do not store the initial data set in a separate object using useState, all non-matches will be gone forever once the filters have been applied.
What would be the best way to achieve this?
DISCLAIMER: I know how to filter arrays in JavaScript and am fully aware of functions such as filter, map, sort, etc. This question is about the React ecosystem and what would be the cleanest, simplest and best way to structure my code in a React application. I know that I will still have to use Array.prototype.filter in my reducer when I write the code for it.
You are asking for opinions so here is mine. React state (whether it’s useState or useReducer) should only be used for stateful values — the values which change and cause your rendered data to change. The filtered list of restaurants is not a stateful value. The filters are a stateful value, and the filtered list is derived from that state. The filtered list should be a const that you generate on each render. If there are other states or props in your component that change which do not effect the filtered list, you can use the useMemo hook to memoize the filtered list to prevent unnecessary recalculations.
Pseudo-code
import React, {useMemo} from “react”;
import {useLocation} from “react-router-dom”;
export const MyComponent = ({restaurants}) => {
const location = useLocation();
const filteredRestaurants = useMemo( () => {
return restaurants.filter( /* some code */ );
}, [location, restaurants] ); //dependency on location and restaurants
return (
<Wrapper>
<EditFiltersComponent />
<List>
{filteredRestaurants.map( restaurant =>
<Restaurant {...restaurant} />
)}
</List>
</Wrapper>
)
}
Generally we get data from props
const OurComponent = ({data, filter}) => { . . .
or
const { filter } = useParams(); // via query string
Also you may have filter input fields in this component then store their values in a local state for a rerender.
For a complex filter is good idea to have filter function:
const filterFunc = ({address}) => {
address.toLowerCase().includes(filter.address.toLowerCase());
}
and render will be like
return (
<ul>
{
data.filter(filterFunc).map({id, name, address} => (
<li key={id}>{`${name} - ${address}`}</li>
))
}
</ul>
)
I used this method
import React, { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import swal from 'sweetalert';
import axios from 'axios';
const Users = () => {
const navigate = useNavigate();
const [users, setUsers] = useState([]);
const [mainUsers, setMainUsers] = useState([]);
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/users').then(res => {
setUsers(res.data);
setMainUsers(res.data);
}).catch(err => {
console.log(err);
})
}, []);
const handleSearch = (e) => {
setUsers(mainUsers.filter(u => u.name.toLowerCase()
.includes(e.target.value.toLowerCase())
));
console.log(e.target.value);
}
return (
<div className={`mt-5 p-4 container-fluid`}>
<div className='row my-2 mb-4 justify-content-between w-100 mx-0'>
<div className='form-group col-10 col-md-6 col-lg-4'>
<input type="text" className='form-control shadow'
placeholder='Search' onChange={handleSearch} />
</div>
</div>
{users.length ? (
<table className='table'>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">User Name</th>
<th scope="col">Email</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{users.map(u =>
<tr key={u.id}>
<th scope="row">{u.id}</th>
<td>{u.name}</td>
<td>{u.username}</td>
<td>{u.email}</td>
<td>
<i className='fa fa-edit text-warning'></i>
<i className='fa fa-edit text-warning pointer'></i>
</td>
</tr>
)}
</tbody>
</table>
) : (
<h6 className='text-center text-info'>wating ...</h6>
)}
</div>
)
}
export default Users;
I know how to make multiple handlers for multiple input fields but I would like to create just one handler working for dynamic number of input fields and I got stuck. I am unable to modify the inputs and they have fixed value.
This is the state of the main component
this.state = {
assetAccounts:["Cash","Financial Investment","Real Estate","Private Business Value"],
accounts:[0,1,2,3]
}
this.onChangeAccount = this.onChangeAccount.bind(this)
This is the handler
onChangeAccount = (e,index) => {
const value = e.target.value;
let data = this.state.accounts;
data[index]=value;
console.log(data)
this.setState({
accounts: [...data]
})
}
and this is rendered:
<table>
{this.state.assetAccounts.map((account,index)=>(<Account value={this.state.accounts[index]} name={account} key={index} onChange={this.onChangeAccount.bind(this)}/>))}
</table>
Account is my own component:
import React from 'react';
class Account extends React.Component {
render() {
return (
<div>
<tr>
<td width="200px">{this.props.name}</td>
<td>
<input
type="text" value={this.props.value}
className="form-control"
onChange={this.props.onChange}
/>
</td>
</tr>
</div>
);
}
}
export default Account;
If anyone knows how to make each of the individual accounts possible to edit separately, I would appreciate it a lot. Thanks for your help!
Prop onChange of component Account only accept one paramater e.
You should pass parameter index when use Account.
Just do like this:
<table>
{this.state.assetAccounts.map((account,index)=>(<Account value={this.state.accounts[index]} name={account} key={index} onChange={e => this.onChangeAccount(e, index)}/>))}
</table>
Actually, pass e.target.value as a parameter for onChange in Account instead of e is better.
In most cases, having a parent tag isn't an issue.
React.createClass({
render: function() {
return (
<tbody>
<tr><td>Item 1</td></tr>
<tr><td>Item 2</td></tr>
</tbody>
);
}
});
But there are some cases where it makes sense to have sibling elements in one render function without a parent, and especially in the case of a table, you don't want to wrap a table row in a div.
React.createClass({
render: function() {
return (
<tr><td>Item 1</td></tr>
<tr><td>Item 2</td></tr>
);
}
});
The second example gives the following error: Adjacent XJS elements must be wrapped in an enclosing tag while parsing file.
How can I render two sibling elements without wrapping them in a <div> or something similar?
This is a limitation currently, but will likely be fixed at some point in the future (there's some open issues on the github repo).
For now, you can use a function which returns an array (this is basically a stateless component):
function things(arg, onWhatever){
return [
<tr><td>Item 1</td></tr>,
<tr><td>Item 2</td></tr>
];
}
And use that in your component.
return (
<table><tbody>
{things(arg1, this.handleWhatever)}
{things(arg2, this.handleWhatever)}
</tbody></table>
);
Update
In React 16 you will be able to return an array from render.
Another Update
You can now either return a top level array, or use <React.Fragment>.
With an array we need to place a key on each item, as React doesn't know that the two elements are constant, instead of a dynamically created list:
function RowPair() {
return [
<tr key="first"><td>First</td></tr>,
<tr key="second"><td>Second</td></tr>,
]
}
With React.Fragment, it behaves much more like wrapping it in a <div> or similar, where a key isn't required if we're not building the children dynamically. First, we can wrap the array in a Fragment:
function RowPair() {
return <React.Fragment>{[
<tr key="first"><td>First</td></tr>,
<tr key="second"><td>Second</td></tr>,
]}</React.Fragment>
}
And then we can eliminate the array and keys entirely:
function RowPair() {
return <React.Fragment>
<tr><td>First</td></tr>
<tr><td>Second</td></tr>
</React.Fragment>
}
I know this has been an old post, but maybe my answer could be a help for newbies like me.
In React 16.2, improved support for Fragments was added.
You can now return it like this:
return (
<>
<tr><td>Item 1</td></tr>
<tr><td>Item 2</td></tr>
</>
);
You can wrap it with <></> or <Fragment></Fragment>.
If you would like to pass some attributes, only key is supported at the time of writing, and you'll have to use <Fragment /> since the short syntax <></> doesn't accept attributes.
Note: If you are going to use the short syntax, make sure that you are using Babel 7.
Source Reference
Woohoo! The React team finally added this feature. As of React v16.0, you can do:
render() {
// No need to wrap list items in an extra element!
return [
// Don't forget the keys :)
<tr key="a"><td>Item 1</td></tr>,
<tr key="b"><td>Item 2</td></tr>
];
}
See the full blog post explaining "New render return types: fragments and strings" here.
Having a parent element is helpful in most cases, as for example, you can have a parent className which can target children elements style and a few other scenarios...
But, if you really don't want to do that, you can use React.Fragment
So simply do something like this:
<React.Fragment>
<First />
<Second />
<Third />
</React.Fragment>
From version 16.2, there is a shortened version also using <>, which look like this in render function:
render() {
return (
<>
<First />
<Second />
<Third />
</>
);
}
Also, if using version 16.0 and above, you can return array of elements which doesn't need parent wrapper also like below:
render() {
return [
<h1 key="heading">Hello from Alireza!</h1>,
<p key="first">Here where I'm!</p>,
<p key="second">And again here :)</p>
];
}
We can render two sibling components by wrapping them inside React.Fragment. For e.g.
ReactDOM.render(
<React.Fragment>
<Item1/>
<Item2/>
</React.Fragment>,document.getElementById('root')
);
There is even a shorter hand for this though.
ReactDOM.render(
<>
<Item1/>
<Item2/>
</>,document.getElementById('root')
);
Wrapping the components inside the React.Fragment does not add extra nodes to DOM.
You can wrap it to the brackets like this:
React.createClass({
render: function() {
return (
[
<tr><td>Item 1</td></tr>
<tr><td>Item 2</td></tr>
]
);
}
});
This example is work well for me:
let values = [];
if (props.Values){
values = [
<tr key={1}>
<td>props.Values[0].SomeValue</td>
</tr>
,
<tr key={2}>
<td>props.Values[1].SomeValue</td>
</tr>
];
}
return (
<table className="no-border-table table">
<tbody>
<tr>
<th>Some text</th>
</tr>
{values}
</tbody>
</table>
)
Something like this syntax worked for me
this.props.map((data,index)=>{return( [ <tr>....</tr>,<tr>....</tr>];)});
For those, who uses TypeScript, the correct syntax is:
return [
(
<div>Foo</div>
),
(
<div>Bar</div>
)
];