I'm attempting to display a json array of data within a blueprintjs table. The table should be dynamic in it's rendering (number of rows and columns), so to display whatever is in the json array. In production, the json array will come from an API call, but to start off with, I'm just trying to get it working on some dummy data.
I've managed to generate the table dynamically and display the column headers, however I'm stuck on generating the actual data cells.
Here's my code so far:
interface ResultsTableProps {}
interface ResultsTableState {
numResultsRows? : number,
results
}
export default class ResultsTable extends React.Component
<ResultsTableProps, ResultsTableState> {
public state: ResultsTableState = {
numResultsRows: 0,
results: null
}
componentDidMount() {
var searchString = store.getState().submitSearch.searchString;
// Pretend an API call just happened and the results object is returned
// This is the dummy data
var resultsObject = getData();
this.setState({
numResultsRows: resultsObject.length,
results: resultsObject
});
}
private createColumn(columnData) {
return <Column name={columnData} />
}
private createColumns(columnDatas) {
return Object.keys(columnDatas[0]["_source"]).map(this.createColumn);
}
private createTable(results, numResultsRows) {
return (
<Table numRows={numResultsRows}>
{this.createColumns(results)}
</Table>
);
}
render() {
return (
<div id="results-table">
<Card interactive={false} elevation={Elevation.TWO} className={"pt-dark"}>
{this.createTable(this.state.results, this.state.numResultsRows)}
</Card>
</div>
);
}
}
When this code runs, I get a table, with the correct number of rows and the correct number of columns, and also with correct column headers.
I now need to somehow fill in the rows with the cells/data, and I'm stuck. I'm not sure how I should go about this.
How can it be done?
In case you'd like to see the dummy data:
[
{
"_type": "location",
"_id": "5sXFcmEBsayGTsLx1BqB",
"_source": {
"elevation": "",
"country": "ZA",
"geonameid": "958386",
"timezone": "Africa/Johannesburg",
"latitude": "-27.17494",
"mod_date": "2014-10-01",
"dem": "968",
"admin1_fips": "08",
"population": "0",
"alternatenames": "",
"feature_class": "S",
"geohash": "k7pt6ubwx0n0",
"name": "Sahara",
"alt_cc": "",
"fulltext": "ZA 958386 Africa/Johannesburg -27.17494 2014-10-01 968 08 0 S Sahara DC8 Sahara FRM NC083 21.91872",
"admin2": "DC8",
"asciiname": "Sahara",
"feature_code": "FRM",
"admin3": "NC083",
"longitude": "21.91872",
"admin4": ""
}
}
]
Note I'm only interested in display the data in the _source key. So the names of my columns are "elevation", "country", "geonameid", etc. And the cell data should be the values of those keys. My real dummy data actually has about 20 of these objects in the array, but i've just shown one for brevity.
Here is a more complete example.
const data = [
{foo: {bar: "baz"}},
...
]
// allows access to a deep object property by name
const getNestedProp = (obj, path) => (
path.split('.').reduce((acc, part) => acc && acc[part], obj)
)
const objectCellRenderer = (key) => (rowIndex) => {
return <Cell>{getNestedProp(data[rowIndex], key)}</Cell>
When rendering the table, define your column cellRenderer like this:
<Column name="someColumn" cellRenderer={objectCellRenderer("foo.bar")}/>
Instead of only passing they key, may pass key and value:
private createColumns(columnDatas) {
return Object.entries(columnDatas[0]["_source"]).map(this.createColumn);
}
Now you can get it like this:
private createColumn([key, value]) {
//...
}
You are just missing cellRenderer prop.
const createCell = (columnData) => (rowIndex) => {
return (
<Cell key={""}>{data[rowIndex][columnData]}</Cell>
);
};
Complete code =>
const SomeTable = () => {
const [data, setData] = useState("");
const [numDataRows, setNumDataRows] = useState(0);
useEffect(() => {
return
let fetchedData = []
// here you fetch data from somewhere
...
setData(fetchedData);
setNumDataRows(fetchedData.length);
});
}, []);
const createCell = (columnData) => (rowIndex) => {
return (
<Cell key={rowIndex + columnData}>{data[rowIndex][columnData]}</Cell>
);
};
const createColumn = (columnData, colIndex) => {
return (
<Column
name={columnData}
key={colIndex}
cellRenderer={createCell(columnnData)}
/>
);
};
const createColumns = (columnsData) => {
return columnsData ? Object.keys(columnsData[0]).map(createColumn) : [];
};
const CreateTable = (data, numDataRows) => {
return (
<Table numRows={numPlayersRows}>
{createColumns(data)}
</Table>
);
};
return <>{CreateTable(data, numDataRows)}</>;
};
export default SomeTable;
Related
I need to remove commas which are in an array of odata objects. Each item has a description which sometimes contains commas. I need to remove those commas.
Here is what I've attempted:
Parent Component:
<CSVComponent capxs={allCaps} />
Child Component:
import { CSVLink } from "react-csv";
export interface ICSVComponentProps {
capxs: IListItem[];
}
export const CSVComponent: React.FunctionComponent<ICSVComponentProps> = (props: ICSVComponentProps) => {
const [results, setResults] = React.useState([]);
const headers = [
{ label: "Id", key: "id" },
{ label: "Title", key: "title" },
{ label: "Description", key: "description" },
];
const getCaps = (c: IListItem[]) => {
const results = [];
for (let i = 0; i <= c.length - 1; i++) {
results[i] = {
id: props.capxs[i].Id,
title: props.capxs[i].Title,
description: props.capxs[i].Description.replace(",",""),
};
}
setResults(results);
};
return (
<DefaultButton className={styles.csvButton}
onClick={() => getCaps(props.capxs)}
>
<CSVLink data={results} headers={headers}>
Create CSV
</CSVLink>
</DefaultButton>
);
Update:
I've updated the code sample above with the Parent component props pass down and more of the child component.
Here is an image showing the array that is created and stored in state. This state is then used by react-csv:
The description value in each item in the array has the comma removed, but react-csv seems to ignore this and it detects the commas, therefore creating incorrect columns in the produced csv file.
If I understand correctly what you need
const memoizedCaps = useMemo<IListItem[]>(() => {
return caps.map(el => ({ ...el, description: el.description.replace(',', '') }));
}, [caps]);
const getCaps = () => {
setResults(memoizedCaps);
};
This issue has been resolved by using:
const getCaps = (caps: IListItem[]) => {
const results = [];
for (let i = 0; i <= caps.length - 1; i++) {
results[i] = {
description: props.capxs[i].Description.replace(/,/g, '""'),
};
}
setResults(results);
};
Not ideal but it's a limitation of react-csv.
Ref: https://github.com/react-csv/react-csv/issues/176
Thanks for all your help.
i have a json file as a server, i need filter the data json when i click in a button, example, in the Navbar i have:
const NavBar = ({setSearchValue, setType}) => {
const handleType = (heroType) =>{
setType(heroType)
}
return (
// this input is ok, i can search data from here
<input id='searchInput' placeholder='Search' onChange={(event) => {setSearchValue(event.target.value)}}/>
//these are the buttons
<Nav.Link onClick={() => handleType('All')}>All Heroes</Nav.Link>
<Nav.Link onClick={() => handleType('Flying')} >Flying Heroes</Nav.Link>
<Nav.Link onClick={() => handleType('Flightless')}>Flightless Heroes</Nav.Link>
and this is where i need to show it
//import Navbar
import NavBar from "./NavBar";
const Home = () => {
// saved the data i need to show
const [content, setContent] = useState();
// saved the searchvalue of navbar, its ok.
const [searchValue, setSearchValue] = useState("");
// i tried save the button data here, next with a IF function i tried to show it, no work
const [type, setType] = useState("Flying");
useEffect(() => {
// get json dta
const getData = async () => {
const response = await db.get("data");
let data = response.data.filter((val) => {
// if no searchValue, return all
if (searchValue === "") return val;
//if searchVale, return coincidences
else if (val.nombre.toLowerCase().includes(searchValue.toLowerCase()))
return val;
});
// returns bootstrap rows depending on number of elements
const rows = [...Array(Math.ceil(data.length / 3))];
const imagesRows = rows.map((row, idx) =>
data.slice(idx * 3, idx * 3 + 3)
);
//saved all in setContent
setContent(
//code
)
getData();
}, [searchValue]);
return (
<>
<NavBar setSearchValue={setSearchValue} setType={setType} />
//show content
<Container>{content >= 0 ? <p>No results</p> : content}</Container>
</>
);
};
I've tried a lot of things, i think i need change a lot of code i a want this work.
Any help would be extremely appreciated.
EDIT
Json:
{
"data": [
{
"id": 0,
"nombre": "Punisher",
"puedeVolar": false,
"nombreReal": "Frank Castle",
"avatarURL": ""
},
{
"id": 1,
"nombre": "Green Arrow",
"puedeVolar": false,
"nombreReal": "Oliver Jonas Queen",
"avatarURL": ""
},
{
"id": 2,
"nombre": "Human Torch",
"puedeVolar": true,
"nombreReal": "Jonathan Lowell Spencer",
"avatarURL": ""
},
{
"id": 3,
"nombre": "Martian Manhunter",
"puedeVolar": true,
"nombreReal": "J'onn J'onzz",
"avatarURL": ""
},
{
"id": 4,
"nombre": "Aquaman",
"puedeVolar": false,
"nombreReal": "Arthur Curry",
"avatarURL": ""
}
}
So when clicked button display heroes.puedeVolar === false or display heroes.puedeVolar === true, depending of the button clicked
Post your JSON to help you.
You're lifting up correctly the states, you just need to do the same that you did with the input.
Put the type as a dependecie of useEffect and inside it filter your JSON with your type value.
useEffect(()=>{
//... Keep your working code
let data = response.data.filter((val) => {
//Keep your working code or refactor it
//Add you new filter params
if (val.type === type) return val;
}
}, [type, searchValue]);
I have a element like :
const DropdownElements = [
{
key: 1,
title: "Şehir",
placeholder: "Şehir Seçiniz",
apiUrl: "https://api.npoint.io/995de746afde6410e3bd",
type: "city",
selecteditem: "",
data : [],
},
{
key: 2,
title: "İlçe",
placeholder: "İlçe Seçiniz",
apiUrl: "https://api.npoint.io/fc801dbd3fc23c2c1679", // its my apis. They hold datas from json
type: "district",
selecteditem: "",
data : [],
},
]
I fetching that url in App in useEffect.
const App = () => {
useEffect(() => {
DropdownElements.map((x) => {
fetch(x.apiUrl)
.then((z) => z.json())
.then((vb) => {
x.data=vb // If i write x.data.push(vb) i can see it on my component but its not giving pure array.
console.log(x.data) // I can see my datas perfectly. I trying fill my data.
});
});
}, []);
And i setting it like that :
<Space>
{DropdownElements.map((x) => {
return (
<PickerCompanent
showSearch
selecteditem={idhold}
key={x.key}
placeholder={x.placeholder}
type={x.type}
datasource={x.data} // I gave my datasource x.data that i filled .
onFocus={onFocus}
onChange={z=>onChange(z)}
onFocus={onFocus}
onSearch={onSearch}
></PickerCompanent>
);
})}
</Space>
But in my component when i try write like console.log(props) my datasource is empty array. How can i see my datas on my component ? I need set my array to a state in my component.
It seems like you aren't using any kind of state in your code.
const App = () => {
const [myData, setMyData] = useState();
useEffect(() => {
DropdownElements.map((x) => {
fetch(x.apiUrl)
.then((z) => z.json())
.then((vb) => {
x.data=vb // If i write x.data.push(vb) i can see it on my component but its not giving pure array.
console.log(x.data) // I can see my datas perfectly. I trying fill my data.
// in here you'll want to be adding your data to some state
// e.g.
setMyData(x.data);
});
});
}, []);
Then within your component, use that state:
datasource={myData}
Your object is updating but not view. To achieve this you need have a component state, to which we can update and trigger return again to update view.
const App = () => {
const [myData, setMyData] = useState(DropdownElements);
useEffect(() => {
myData.map((x, i) => {
fetch(x.apiUrl)
.then((z) => z.json())
.then((result) => {
myData[i].data = result;
setMyData(myData);
});
});
}, []);
return (
<Space>
{myData.map((x) => {
return (
<PickerCompanent
showSearch
selecteditem={idhold}
key={x.key}
placeholder={x.placeholder}
type={x.type}
datasource={x.data} // I gave my datasource x.data that i filled .
onFocus={onFocus}
onChange={z=>onChange(z)}
onFocus={onFocus}
onSearch={onSearch}
></PickerCompanent>
);
})}
</Space>
);
I have one JSON file named Test.json with all data in it.
[ { "name" : "Margo",
"birthDate": "1990.03.15",
"timetable": [
{"time": "8.00",
"task": "toDoMargoElem1"},
{"time": "9.00",
"task": "toDoMargoElem2"}
},
{ "name" : "Arthur",
"birthDate": "1990.03.15",
"timetable": [
{"time": "8.00",
"task": "toDoArthurElem1"},
{"time": "9.00",
"task": "toDoArthurElem2"}
}
}
I'd like to use call data from component, so I tried to call GraphiQL. Code exporter gives me
const ComponentName = () => {
const data = useStaticQuery(graphql`
{
allTestJson {
edges {
node {
name
timetable {
time
task
}
}
}
}
}
`)
return <pre>{JSON.stringify(data, null, 4)}</pre>
}
In my component Mycomponent I did next
import React from 'react'
import {useStaticQuery, graphql} from 'gatsby'
export default function Sched() {
const data = useStaticQuery(graphql`
{
allTestJson {
edges {
node {
name
timetable {
time
task
}
}
}
}
}
`)
const results = data.allTestJson.edges.map (({node}) => {
const {name, time, task} = node;
return {
name,
time,
task
}
})
return (<div>
{results.map (({eventName, time, task})=>
<div key = {name}>
{name}
{time}
{task}
</div>
)}
</div>
)
}
But as a result i see just construction like
<div> {name} </div>
<div> {name} </div>
How can I see {time, task}?
Why map doesn't show all nodes of my objects?
Check the object desctructing
const results = data.allTestJson.edges.map (({node}) => {
const {name, time, task} = node; // check here
const {name, timetable:{time, task}} = node; // this is an example of nested object destructing
return {
name,
time,
task
}
})
time and task are inside timetable nested object so the resulting code should look like:
import React from 'react'
import {useStaticQuery, graphql} from 'gatsby'
export default function Sched() {
const data = useStaticQuery(graphql`
{
allTestJson {
edges {
node {
name
timetable {
time
task
}
}
}
}
}
`)
return data.allTestJson.edges.map (({node}) => {
const {name, timetable} = node;
return <div key = {name}>
{name}
{timetable.map(item=><div key={item.task}>{item.time},{item.task}</div>)}
</div>
})
}
birthday field is not being queried (I guess it's a typo) so add it in the correct position.
I want to filter the data from my multiple states at one time. But I am getting the data of only second state.
I have two states and both states are getting seprate data from seprate apis. Now I want to filter the data from it. thank youI don't know what i m doing wrong so pls help me and look at my code.
searchFeatured = value => {
const filterFeatured = (
this.state.latestuploads || this.state.featuredspeakers
).filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
this.setState({
featuredspeakers: filterFeatured,
latestuploads: filterFeatured
});
};
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
featuredspeakers: [],
latestuploads: [],
};
}
componentDidMount() {
axios
.all([
axios.get(
'https://staging.islamicmedia.com.au/wp-json/islamic-media/v1/featured/speakers',
),
axios.get(
'https://staging.islamicmedia.com.au/wp-json/islamic-media/v1/featured/latest-uploads',
),
])
.then(responseArr => {
//this will be executed only when all requests are complete
this.setState({
featuredspeakers: responseArr[0].data,
latestuploads: responseArr[1].data,
loading: !this.state.loading,
});
});
}
Using the || (OR) statement will take the first value if not null/false or the second. What you should do is combine the arrays
You should try something like
[...this.state.latestuploads, ... this.state.featuredspeakers].filter(item=>{});
Ahmed, I couldn't get your code to work at all - searchFeatured is not called anywhere. But I have some thoughts, which I hope will help.
I see that you're setting featuredspeakers and latestuploads in componentDidMount. Those are large arrays with lots of data.
But then, in searchFeatured, you are completely overwriting the data that you downloaded and replacing it with search/filter results. Do you really intend to do that?
Also, as other people mentioned, your use of the || operator is just returning the first array, this.state.latestuploads, so only that array is filtered.
One suggestion that might help is to set up a very simple demo class which only does the filtering that you want. Don't use axios at all. Instead, set up the initial state with some mocked data - an array of just a few elements. Use that to fix the filter and search functionality the way that you want. Here's some demo code:
import React from 'react';
import { Button, View, Text } from 'react-native';
class App extends React.Component {
constructor(props) {
super(props);
this.searchFeatured = this.searchFeatured.bind(this);
this.customSearch = this.customSearch.bind(this);
this.state = {
loading: false,
featuredspeakers: [],
latestuploads: [],
};
}
searchFeatured = value => {
// overwrite featuredspeakers and latestuploads! Downloaded data is lost
this.setState({
featuredspeakers: this.customSearch(this.state.featuredspeakers, value),
latestuploads: this.customSearch(this.state.latestuploads, value),
});
};
customSearch = (items, value) => {
let searchTermLowercase = value.toLowerCase();
let result = items.filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
return result;
}
handlePress(obj) {
let name = obj.name;
this.searchFeatured(name);
}
handleReset() {
this.setState({
featuredspeakers: [{ name: 'Buffy', title: 'Slayer' }, { name: 'Spike', title: 'Vampire' }, { name: 'Angel', title: 'Vampire' }],
latestuploads: [{ name: 'Sarah Michelle Gellar', 'title': 'Actress' }, { name: 'David Boreanaz', title: 'Actor' }],
loading: !this.state.loading,
});
}
componentDidMount() {
this.handleReset();
}
getList(arr) {
let output = [];
if (arr) {
arr.forEach((el, i) => {
output.push(<Text>{el.name}</Text>);
});
}
return output;
}
render() {
let slayerList = this.getList(this.state.featuredspeakers);
let actorList = this.getList(this.state.latestuploads);
return (
<View>
<Button title="Search results for Slayer"
onPress={this.handlePress.bind(this, {name: 'Slayer'})}></Button>
<Button title="Search results for Actor"
onPress={this.handlePress.bind(this, {name: 'Actor'})}></Button>
<Button title="Reset"
onPress={this.handleReset.bind(this)}></Button>
<Text>Found Slayers?</Text>
{slayerList}
<Text>Found Actors?</Text>
{actorList}
</View>
);
}
};
export default App;
You should apply your filter on the lists separately then. Sample code below =>
const searchFeatured = value => {
this.setState({
featuredspeakers: customSearch(this.state.featuredspeakers, value),
latestuploads: customSearch(this.state.latestuploads, value)
});
};
const customSearch = (items, value) => {
return items.filter(item => {
let featureLowercase = (item.name + " " + item.title).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return featureLowercase.indexOf(searchTermLowercase) > -1;
});
}