I get data from github api
I have all the data i need to display, but I want to splice it so that i only get 20 repositories per page.
And I don't want a framework or a plugin for that.
I'm fairly new to React and JS in general so I don't know where to start or what to do next to create a pagination.
import React, {Component} from 'react';
import axios from 'axios';
class Apirequest extends Component {
constructor(){
super();
this.state = {
githubData: [],
};
}
componentDidMount() {
axios.get('https://api.github.com/search/repositories?q=language:javascript&sort=stars&order=desc&per_page=100')
.then(res => {
console.log('res', res)
this.setState({ githubData: res.data.items})
})
}
render() {
const { githubData } = this.state
return(
<div className="container">
{githubData.map((name, index) =>
<table key={name.id}>
<tr>
<th><img src={name.owner.avatar_url}/></th>
<td>{name.owner.login}<div className="border-bottom"></div></td>
<td>{name.description}<div className="border-bottom"></div></td>
<td><a href={name.homepage}>{name.homepage}</a></td>
</tr>
</table>
)}
</div>
)
}
}
export default Apirequest;
First of all your map function has a wrong logic. You are creating a table for each record and you should only create a row for each record. table tags should be outside of map.
render() {
const { githubData } = this.state
return(
<div className="container">
<table key={name.id}>
{githubData.map((name, index) =>
<tr>
<th><img src={name.owner.avatar_url}/></th>
<td>{name.owner.login}<div className="border-bottom"></div></td>
<td>{name.description}<div className="border-bottom"></div></td>
<td><a href={name.homepage}>{name.homepage}</a></td>
</tr>
)}
</table>
</div>
)
}
For pagination what you can do is to limit the number of rows you show by using Array.prototype.slice(). Just to give you an idea I am posting a small example. You might need to implement some more for this logic to work on your code.
Example
previousPage = () => {
if (this.state.currentPage !== 1)
this.setState((prevState) => ({currentPage: (prevState.currentPage - 1)}))
}
nextPage = () => {
if (this.state.currentPage + 1 < this.state.githubData.lenght)
this.setState((prevState) => ({currentPage: (prevState.currentPage + 1)}))
}
render() {
const { githubData, currentPage } = this.state
return(
<div className="container">
<table key={name.id}>
{githubData.slice((currentPage * 20), 20).map((name, index) =>
<tr>
<th><img src={name.owner.avatar_url}/></th>
<td>{name.owner.login}<div className="border-bottom"></div></td>
<td>{name.description}<div className="border-bottom"></div></td>
<td><a href={name.homepage}>{name.homepage}</a></td>
</tr>
)}
</table>
<button onClick={this.previousPage}>Previous Page</button>
<button onClick={this.nextPage}>Next Page</button>
</div>
)
}
set your state to have pagination info and data.
such as
state = {
pagination: {
start: 0,
rows: 20
},
githubData: ....
}
and now in you render function you can splice based on the pagination info. Anytime new page is clicked, you can set state to new start variable
Related
So I have a table I made in a react app, it's currently just rows and columns. I want to add a basic search feature, where a user can type a name and get rows matching that name. I've looked at some examples online, but nothing covers how to add a search feature with the type of table I made. Any tips or knowledge of how to do this given the code I have.
import React from "react";
import './App.css';
class App extends React.Component {
// Constructor
constructor(props) {
super(props);
this.state = {
items: [],
DataisLoaded: false
};
}
// ComponentDidMount is used to
// execute the code
componentDidMount() {
fetch(
"http://ec2-34-213-215-13.us-west-2.compute.amazonaws.com:3001/getPatients")
.then((res) => res.json())
.then((json) => {
this.setState({
items: json,
DataisLoaded: true
});
})
}
render() {
const { DataisLoaded, items } = this.state;
if (!DataisLoaded) return <div>
<h1> Please wait some time.... </h1> </div> ;
return (
<div className = "App">
<h1> Welcome to the Master Patient Index </h1> {
<table class="center">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>DOB</th>
<th>Gender</th>
<th>SSN</th>
<th>Race</th>
<th>Ethnicity</th>
<th>Marital</th>
<th>Drivers License</th>
<th>Passport</th>
<th>Address</th>
<th>City</th>
<th>State</th>
<th>County</th>
<th>Zip</th>
</tr>
{items.map((items, key) => {
return (
<tr key={key}>
<td>{items.FIRST}</td>
<td>{items.LAST}</td>
<td>{items.BIRTHDATE}</td>
<td>{items.GENDER}</td>
<td>{items.SSN}</td>
<td>{items.RACE}</td>
<td>{items.ETHNICITY}</td>
<td>{items.MARITAL}</td>
<td>{items.DRIVERS}</td>
<td>{items.PASSPORT}</td>
<td>{items.ADDRESS}</td>
<td>{items.CITY}</td>
<td>{items.STATE}</td>
<td>{items.COUNTY}</td>
<td>{items.ZIP}</td>
</tr>
)
})}
</table>
}
</div>
);
}
}
export default App;
I would do this by adding a new property to your state called searchTerm.
constructor(props) {
super(props);
this.state = {
items: [],
searchTerm: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
Then add an input field to update the searchTerm
<input placeholder="search here..." value={searchTerm} name="searchTerm" onChange={this.handleChange} />
Then using the .filter array method to filter by search term
{items
.filter(items => items.FIRST.toLowerCase().includes(searchTerm.toLowerCase()))
.map((items, key) => {
return (
<tr key={key}>
<td>{items.FIRST}</td>
</tr>
)
})}
Full code: (simplied version of your code)
https://codesandbox.io/s/cool-bird-i2nj5o?file=/src/App.js:844-1183
I wanted to remove a row from the table with the function 'deleteRow(btn)' when pressing the button, but I get this error 'Cannot read properties of undefined (reading 'parentNode')'. What could I add or correct to successively drop a row from a table?
App.js
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
fotos: [],
restaurantes:[],
}
}
deleteRow=(btn)=> {
var row = btn.parentNode.parentNode;
row.parentNode.removeChild(row);
}
render(){
const { fotos, restaurantes } = this.state;
<div className="container">
<Tabela dadosFotos={fotos} restaurante={this.deleteRow} />
</div>
}
Tabela.js
import React from "react";
const CorpoTabela = (props) => {
const rows = props.dadosDasFotos.map((row) => {
return(
<tr key={row.idFoto}>
<td>{row.nomeRestaurante}</td>
<td>
<button className="btn btn-outline-danger"
onClick={()=>props.restauranteAremover(row.idFoto)}>
Delete restaurante
</button>
</td>
</tr>
)
})
return(<tbody>{rows}</tbody>)
}
class Tabela extends React.Component{
render(){
const { dadosFotos, restaurante }=this.props
return(
<table className="table table-striped">
<CorpoTabela dadosDasFotos={dadosFotos} restauranteAremover={restaurante}/>
</table>
)
}
}
You should not be manipulating the DOM as this is an anti-pattern in React, instead you should update the state you are rendering from.
Delete by idFoto.
deleteRow = (idFoto)=> {
this.setState(prevState => ({
fotos: prevState.fotos.filter(el => el.idFoto !== idFoto
}))
}
In the child pass the id to the delete handler.
<button className="btn btn-outline-danger"
onClick={() => props.restauranteAremover(row.idFoto)}>
Delete restaurante
</button>
In React, you generally want to try avoiding direct DOM manipulation, since this takes state management out of React, which is something you want to avoid.
Therefore, instead of trying to delete each row directly using DOM functions like remove or removeChild, it would be best to keep all of the table rows in a key in the state object. Then, you can filter out the deleted row by filtering it out by index or through some other identifier. Here's an example:
import { Component } from 'react'
import './styles.css'
export default class App extends Component {
state = {
rows: [
{ id: 1, col1: 'A', col2: 'some text' },
{ id: 2, col1: 'B', col2: 'some text' }
]
}
spliceRow = (index) => {
this.state.rows.splice(index, 1)
this.setState({ rows: this.state.rows })
}
filterRows = (id) => {
this.setState({
rows: this.state.rows.filter((row) => {
return row.id !== id
})
})
}
render() {
return (
<table className="App">
<tbody>
{this.state.rows.map((row, index) => {
return (
<tr key={row.id}>
<td>{row.col1}</td>
<td>{row.col2}</td>
<td>
<button onClick={() => this.spliceRow(index)}>
Remove row with splice
</button>
</td>
<td>
<button onClick={() => this.filterRows(row.id)}>
Remove row with filter
</button>
</td>
</tr>
)
})}
</tbody>
</table>
)
}
}
I've come to a halt making this covid19 app where I can see a list of countries on the left side of the screen with the option of adding any number of countries to the right side of the screen, which displays more covid data of the added country. I'm also kinda new to React.
Problem is, when I click the add button the added state is updated, and it displays that added country on the right side of the screen. But, when I try adding another country I get an error. I believe the error is somewhere around when I try to setState({ state }) in the addCountry method from within App.js.
In other words, the 'added' state is only letting itself hold no more than one array element. Help much much much appreciated. I posted all the code.
index.js
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.js
import CountryList from "./components/CountryList.js";
import Find from "./components/Find.js";
import Added from "./components/Added.js";
class App extends Component {
constructor() {
super();
this.state = {
countries: [],
inputbox: [],
added: [],
};
}
// Arrow functions capture "this" when they are defined, while standard functions do when they are executed.
// Thus, no need for the bind method. Awesome.
handleChange = (e) =>
this.setState({
inputbox: e.target.value,
});
getCountryData = async (slug) => {
const resp = await fetch(`https://api.covid19api.com/live/country/${slug}`);
var addedData = await resp.json();
// Api returns most days of covid, per country, that it tracks
// Thus, we want the last tracked day of a country
addedData = addedData[addedData.length - 1];
return addedData;
};
// Add a country to the added state
// Call when user clicks button associated with their desired country
addCountry = async (btnId) => {
const { countries, added } = this.state;
var addedData = await this.getCountryData(btnId);
countries.map((country) => {
// If the button ID is equal to the current country in the loops' Slug
if (btnId == country.Slug) {
try {
added.push([
{
addedCountry: addedData.Country,
confirmedTotal: addedData.Confirmed,
deathsTotal: addedData.Deaths,
recoveredTotal: addedData.Recovered,
activeTotal: addedData.Active,
},
]);
// (bug) IT IS PUSHING, BUT ITS NOT SETTING THE STATE!
// ITS ONLY LETTING ME KEEP ONE ITEM IN THE STATE
this.setState({ added });
console.log(added);
} catch (error) {
alert(`Sorry, country data not available for ${country.Country}`);
return;
}
}
});
};
removeCountry = (btnId) => {
const { added } = this.state;
added.map((added, index) => {
//console.log(added[index].addedCountry);
if (btnId == added[index].addedCountry) {
added.splice(index, 1);
this.setState({ added: added });
} else {
console.log("not removed");
return;
}
});
};
// Mount-on lifecycle method
async componentDidMount() {
const resp = await fetch("https://api.covid19api.com/countries");
const countries = await resp.json(); // parsed response
this.setState({ countries }); // set state to parsed response
}
render() {
// Filter out countries depending on what state the inputbox is in
const { countries, inputbox } = this.state;
const filtered = countries.filter((country) =>
country.Country.includes(inputbox)
);
return (
<div className="App Container">
<Find
placeholder="Type to find a country of interest..."
handleChange={this.handleChange}
/>
<div className="row">
<CountryList countries={filtered} addCountry={this.addCountry} />
<Added added={this.state.added} removeCountry={this.removeCountry} />
</div>
</div>
);
}
}
export default App;
Added.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import AddedCountry from "./AddedCountry.js";
class Added extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="col-md-6">
<Table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Country</th>
<th scope="col">Active</th>
<th scope="col">Confirmed Total</th>
<th scope="col">Recovered</th>
<th scope="col">Deaths</th>
<th scope="col">Action</th>
</tr>
</thead>
{this.props.added.map((added, index) => (
<AddedCountry
added={added[index]}
removeCountry={this.props.removeCountry}
/>
))}
</Table>
</div>
);
}
}
export default Added;
AddedCountry.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class AddedCountry extends Component {
constructor(props) {
super(props);
}
render() {
return (
<tbody>
<tr>
<td></td>
<td>{this.props.added.addedCountry}</td>
<td>{this.props.added.activeTotal}</td>
<td>{this.props.added.confirmedTotal}</td>
<td>{this.props.added.recoveredTotal}</td>
<td>{this.props.added.deathsTotal}</td>
<td>
{
<Button
onClick={() =>
this.props.removeCountry(
document.getElementById(this.props.added.addedCountry).id
)
}
id={this.props.added.addedCountry}
type="submit"
color="danger"
size="sm"
>
Remove
</Button>
}
</td>
</tr>
</tbody>
);
}
}
export default AddedCountry;
CountryList.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
import Country from "./Country.js";
class CountryList extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="col-md-6">
<Table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Country</th>
<th scope="col">Actions</th>
</tr>
</thead>
{
// Each country is a component
// Function will display all countries as the Map function loops through them
this.props.countries.map((country) => (
<Country countries={country} addCountry={this.props.addCountry} />
))
}
</Table>
</div>
);
}
}
export default CountryList;
Country.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class Country extends Component {
constructor(props) {
super(props);
}
render() {
return (
<tbody>
<tr>
<td></td>
<td>{this.props.countries.Country}</td>
<td>
{
<Button
onClick={() =>
this.props.addCountry(
document.getElementById(this.props.countries.Slug).id
)
}
id={this.props.countries.Slug}
type="submit"
color="success"
size="sm"
>
Add
</Button>
}
</td>
</tr>
</tbody>
);
}
}
export default Country;
Find.js
import React, { Component } from "react";
import { Table, Form, Input, Button } from "reactstrap";
class Find extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="Find container">
<br />
<Form>
<div className="form-row">
<div className="form-group col-md-6">
<h3>Find a Country</h3>
<Input
type="text"
className="form-control"
id="country"
placeholder={this.props.placeholder}
onChange={this.props.handleChange}
></Input>
</div>
</div>
</Form>
</div>
);
}
}
export default Find;
I haven't pored over all that code, but focusing right where you think the issue is it is obvious you are mutating your state object by pushing directly into the added array.
Solution
Don't mutate state!
Since it seems you only want to add a single new "add" and only when the button's btnId matches a country's slug, and the btnId can only ever be a valid value from the mapped countries array, I think this can be greatly simplified.
addCountry = async (btnId) => {
const addedData = await this.getCountryData(btnId);
if (addedData) {
this.setState(prevState => ({
added: prevState.added.concat({ // <-- concat creates a new array reference
addedCountry: addedData.Country,
confirmedTotal: addedData.Confirmed,
deathsTotal: addedData.Deaths,
recoveredTotal: addedData.Recovered,
activeTotal: addedData.Active,
}),
}));
} else {
alert(`Sorry, country data not available for ${country.Country}`);
}
};
Similarly the removeCountry handler is mis-using the array mapping function and mutating the added state. Array.prototype.filter is the idiomatic way to remove an element from an array and return the new array reference.
removeCountry = (btnId) => {
this.setState(prevState => ({
added: prevState.added.filter(el => el.addedCountry !== btnId),
}));
};
Additional Issues & Suggestions
Added.js
If you maintain the added array as a flat array (not an array of arrays) then it's trivial to map the values.
{this.props.added.map((added) => (
<AddedCountry
key={added}
added={added}
removeCountry={this.props.removeCountry}
/>
))}
Country.js & AddedCountry.js
I don't see any reason to query the DOM for the button id when you are literally right there and can enclose the country slug in the onClick callback.
<Button
onClick={() => this.props.addCountry(this.props.countries.Slug)}
id={this.props.countries.Slug}
type="submit"
color="success"
size="sm"
>
Add
</Button>
<Button
onClick={() => this.props.removeCountry(this.props.added.addedCountry)}
id={this.props.added.addedCountry}
type="submit"
color="danger"
size="sm"
>
Remove
</Button>
App.js
This may or may not matter, but it is often the case to do case-insensitive search/filtering of data. This is to ensure something like "France" still matching a user's search input of "france".
const filtered = countries.filter((country) =>
country.Country.toLowerCase().includes(inputbox.toLowerCase())
);
I have a simple application with two react components:
vacancies.jsx - lists vacancies
counter.jsx - shows the number of vacancies from vacancies.jsx
When the application loads the counter shows the correct number of vacancies, but as I start adding/deleting vacancies in vacancies.jsx the count stays the same.
vacancies.jsx:
export class Vacancy extends React.Component {
constructor(props) {
super(props);
this.state = { vacancies: [], loading: true, title:"" };
fetch('/api/Vacancy/Vacancies')
.then(response => response.json())
.then(data => {
this.setState({ vacancies: data, loading: false });
});
}
delete(id) {
var vacancies = this.state.vacancies;
this.setState(
{
vacancies: this.state.vacancies.filter((v) => {
return (v.id != id);
})
});
}
loadVacancies(vacancies) {
return (
<table className='table table-striped'>
<thead>
<tr>
<th>Title</th>
<th>Min Salary</th>
<th>Max Salary</th>
</tr>
</thead>
<tbody>
{vacancies.map(v =>
<tr key={v.id}>
<td>{v.title}</td>
<td>{v.currency} {v.minSalary}</td>
<td>{v.currency} {v.maxSalary}</td>
<td>
<a href="#" onClick={(id) => this.delete(v.id)}>Delete</a>
</td>
</tr>
)}
</tbody>
</table>
);
}
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: this.loadVacancies(this.state.vacancies);
return (
<div>
{contents}
</div>
);
}
}
const containerElement = document.getElementById('content');
ReactDOM.render(<Vacancy />, containerElement);
counter.jsx
import { Vacancy } from "./vacancies";
export class Counter extends Vacancy {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Items:{this.state.vacancies.length}</h1>
</div>
);
}
}
ReactDOM.render(<Counter />, document.getElementById('counter'));
UI:
According to the react team, they recommend using composition instead of inheritance:
React has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.
And
At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies.
https://reactjs.org/docs/composition-vs-inheritance.html
So based on your code, recreate your Counter component like this:
export const Counter = props => {
return (
<div>Items: {props.items}</div>
);
};
Then just change your Vacancies component including your new Counter component there:
loadVacancies(vacancies) {
return (
<Counter items={this.state.vacancies.length} />
<table className='table table-striped'>
<thead>
<tr>
<th>Title</th>
<th>Min Salary</th>
<th>Max Salary</th>
</tr>
</thead>
<tbody>
{vacancies.map(v =>
<tr key={v.id}>
<td>{v.title}</td>
<td>{v.currency} {v.minSalary}</td>
<td>{v.currency} {v.maxSalary}</td>
<td>
<a href="#" onClick={(id) => this.delete(v.id)}>Delete</a>
</td>
</tr>
)}
</tbody>
</table>
);
}
I am having problem with sorting table column in React. My table composes of three components: one defining the individual row (row.js), one rendering headers and mapping the rows that need own states (rows.js) and finally table.js that renders the whole thing. The data of the table comes from the database.
Here is a part of the row.js:
class ProjectTableProjectRow extends Component {
render() {
const { project } = this.props;
return (
<tr>
<td className="projects">
<Body2>
<Link to={`/projects/${project.id}`}>{project.description}</Link>
</Body2>
</td>
export default withRouter(ProjectTableProjectRow);
And here an excerpt from my rows.js:
class ProjectTableProjectRows extends Component {
componentDidMount() {
this.props.projects.getAll(); // This gets all the projects from the store
}
onSortProjects = () => {
let sortedToBe = this.props.projects.list.map(project => project.description);
const sorted = sortedToBe.sort();
};
render() {
return (
<table>
<thead>
<tr>
<th>
<Caption>Project</Caption>
<IconButton onClick={() => this.onSortProjects()}>
<RowsIcon />
</IconButton>
</th>
</tr>
</thead>
{this.props.projects.list.map(project => (
<tbody key={project.id}>
<ProjectTableProjectRow project={project} />
</tbody>
))}
</table>
);
}
}
export default ProjectTableProjectRows;
Finally, there is a projectTable.js (that I am not sure if I need anyway...)
class ProjectTable extends Component {
render() {
return (
<>
<ProjectTableProjectRows projects={this.props.projects} />
</>
);
}
}
export default ProjectTable;
So, I'd like to sort the project.description column (there are project names as strings) in alphabetical order. Naturally the icon and sort function onClick in it is not connected to the table column, so the sort function does nothing to the table. How can I achieve this? I do not know yet how to think "in React".
This is obviously not tested, I'm just making a couple of updates to the code you had but this will allow you to use the sorted values.
class ProjectTableProjectRows extends Component {
constructor(props) {
super(props)
this.state = {
projects: null
}
}
componentDidMount() {
const projects = this.props.projects.getAll();
this.setState({ projects })
}
onSortProjects = () => {
let sortedToBe = this.state.projects.list.map(project => project.description);
const sorted = sortedToBe.sort();
this.setState({ projects: sorted });
};
render() {
return (
<table>
<thead>
<tr>
<th>
<Caption>Project</Caption>
<IconButton onClick={() => this.onSortProjects()}>
<RowsIcon />
</IconButton>
</th>
</tr>
</thead>
{this.state.projects && this.state.projects.list.map(project => (
<tbody key={project.id}>
<ProjectTableProjectRow project={project} />
</tbody>
))}
</table>
);
}
}
export default ProjectTableProjectRows;