This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 6 years ago.
I am working on this FreeCodeCamp leaderboard table and and when clicking in the highlighted table header the application calls either this url https://fcctop100.herokuapp.com/api/fccusers/top/recent or this one https://fcctop100.herokuapp.com/api/fccusers/top/alltime thus sorting between campers with the highest points for the past 30 days or all time.
My issue here is that I have to click twice in order to get the desired results. In the CamperLeaderboard component handleSort function when I console.log the state does not change until I have clicked twice
Click Once
handleSort = (sort) => {
console.log(sort); // alltime
console.log(this.state.sort) //recent
this.setState({ sort: sort });
console.log(this.state.sort) //recent
this.getData();
};
Click Twice
handleSort = (sort) => {
console.log(sort); // alltime
console.log(this.state.sort) //alltime
this.setState({ sort: sort });
console.log(this.state.sort) //alltime
this.getData();
};
This is the CodePen preview and below is the full code
/**
Table body component
*/
class Table extends React.Component {
handleSort = (e, sort) => {
this.props.handleSort(sort);
};
renderCampers = (key, count) => {
const camper = this.props.campers[key];
return(
<tr key={key}>
<td>{count}</td>
<td>
<a href={`https://www.freecodecamp.com/${camper.username}`} target='_blank'>
<img src={camper.img} />
{camper.username}
</a>
</td>
<td className='center'>{camper.recent}</td>
<td className='center'>{camper.alltime}</td>
</tr>
)
};
render() {
let count = 0;
return (
<div>
<table>
<caption>Leaderboard</caption>
<tr>
<th>#</th>
<th>Camper Name</th>
<th onClick={(e) => this.handleSort(e, 'recent')}><a href='javascript:void(0)'>Points in the past 30 days</a></th>
<th onClick={(e) => this.handleSort(e, 'alltime')}><a href='javascript:void(0)'>All time points</a></th>
</tr>
{Object.keys(this.props.campers).map(key => {
count++;
return this.renderCampers(key, count);
})}
</table>
</div>
);
}
}
/**
Container
*/
class CamperLeaderboard extends React.Component {
state = {
campers: [],
sort: 'recent'
};
getData() {
let url = `https://fcctop100.herokuapp.com/api/fccusers/top/${this.state.sort}`
const self = this;
axios.get(url)
.then(function (response) {
self.setState({ campers: response.data });
//console.log(self.state.campers);
})
.catch(function (error) {
console.log(error);
});
}
componentWillMount() {
this.getData();
}
handleSort = (sort) => {
this.setState({ sort: sort });
this.getData();
};
render() {
return (
<div>
<p>Click links in table header to sort</p>
<Table campers={this.state.campers}
handleSort={this.handleSort} />
</div>
);
}
}
ReactDOM.render(<CamperLeaderboard />, document.getElementById('app'));
/*
To get the top 100 campers for the last 30 days: https://fcctop100.herokuapp.com/api/fccusers/top/recent.
To get the top 100 campers of all time: https://fcctop100.herokuapp.com/api/fccusers/top/alltime.
*/
I believe #finalfreq's explanation is correct and this is how to fix it.
Update handleSort method of the CamperLeaderboard class like this:
handleSort = (sort) => {
this.setState({ sort: sort });
// You don't need to read sort from state. Just pass it :)
this.getData(sort);
}
Related
I am trying to implement search functionality in reactjs. i am not getting how to do. below i have given code which i have tried.
i need to display result in table after serach.
render() {
return (
<div>
<input onChange={this.handleSearchChange} placeholder="Search"/>
</div>
)
}
// below is my function
handleSearchChange = e => {
const { value } = e.target;
var self = this
axios.post("http://localhost:4000/get", { name: value })
.then(function(res){
console.log("detail",res.data)
})
.catch(function(err){
console.log('Error',err)
})
};
//below is my api response
[
{color: "green",name: "test",age: "22"},
{color: "red",name: "test2",age: "23"}
]
Once you have the data you need to add it to state so that when the state changes you can iterate over the data and rerender the view. I'm using React hooks in this example. I hope it helps a little.
table {
background-color: white;
border-collapse: collapse;
}
tr:nth-child(even) {
background-color: #efefef;
}
td {
padding: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script type="text/babel">
// Grab the hooks you need
const { useState, useEffect } = React;
// The main function that will loop over the data
// and create the rows for the table
function createRows(data) {
// Iterate over the data and for each object
// return a new HTML row
return data.map((row, i) => {
const { color, name, age } = row;
return (
<tr key={i}>
<td>{color}</td>
<td>{name}</td>
<td>{age}</td>
</tr>
)
});
}
// Mock API call which returns data after a 2 second delay
function fakeAPICall() {
return new Promise((res, rej) => {
setTimeout(() => {
res('[{"color": "green","name": "test","age": "22"},{"color": "red","name": "test2","age": "23"}]');
}, 2000);
});
}
function Example () {
// Set up the state in the componenent
const [data, setData] = useState([]);
// When the component renders load in the data
// and set that as your state.
useEffect(() => {
async function getData() {
const response = await fakeAPICall();
const data = JSON.parse(response);
setData(data);
}
getData();
}, []);
// If there's no data in state display nothing...
if (!data.length) return <div>No data</div>
// ...otherwise pass the data into the createRows function
// and return them the row data
return (
<table>
<thead>
<th>Color</th>
<th>Name</th>
<th>Age</th>
</thead>
<tbody>
{createRows(data)}
</tbody>
</table>
)
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
</script>
<div id="react"></div>
And here's how I would do it with a class component:
table {
background-color: white;
border-collapse: collapse;
}
tr:nth-child(even) {
background-color: #efefef;
}
td {
padding: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script type="text/babel">
// The main function that will loop over the data
// and create the rows for the table
function createRows(data) {
// Iterate over the data and for each object
// return a new HTML row
return data.map((row, i) => {
const { color, name, age } = row;
return (
<tr key={i}>
<td>{color}</td>
<td>{name}</td>
<td>{age}</td>
</tr>
)
});
}
// Mock API call which returns data after a 2 second delay
function fakeAPICall() {
return new Promise((res, rej) => {
setTimeout(() => {
res('[{"color": "green","name": "test","age": "22"},{"color": "red","name": "test2","age": "23"}]');
}, 2000);
});
}
class Example extends React.Component {
// Set up the state in the componenent
constructor() {
super();
this.state = { data: [] };
}
// When the component renders load in the data
// and set that as your state.
componentDidMount() {
fakeAPICall().then(response => {
return JSON.parse(response);
}).then(data => {
this.setState({ data });
});
}
// ...otherwise pass the data into the createRows function
// and return them the row data
render() {
if (!this.state.data.length) return <div/>
return (
<table>
<thead>
<th>Color</th>
<th>Name</th>
<th>Age</th>
</thead>
<tbody>
{createRows(this.state.data)}
</tbody>
</table>
)
}
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
</script>
<div id="react"></div>
If I understand your question correctly, you are trying to update your table rendering. I will assume, as you mentioned, that your API is working fine at the call.
In React, there is a feature called "state", which can preserve a variable state. The cool thing about it also is that when you change that state, your component re-renders. A little explanation or ReactJS states:
// intialization of a variable "varX" of type integer with value 1
// setVarX(int) is used to change the variable state
const [varX, setVarX] = React.useState(1);
// changing the state
setVarX(3);
So for your problem, we need two things: a state that holds the API response and that we update whenever the API has new data, and a table display of your data.
State
In your function that is rendering (I assume it is name TableView), let's add this state and have it updated in the handler when the API succeeds
function TableView(){
// table data, initialized to empty array
const [tableData, setTableData] = React.useState([]);
// handle updated with set state
handleSearchChange = e => {
const { value } = e.target;
var self = this.axios.post("http://localhost:4000/get", { name: value })
.then(function(res){
console.log("detail",res.data)
setTableData(res.data) // update the table data!
})
.catch(function(err){
console.log('Error',err)
})
};
return (...) // render function in the next section
}
Render
The function would use the map feature of react:
render() {
return (
<div>
<input onChange={this.handleSearchChange} placeholder="Search"/>
<table style="width:100%">
<tr>
<th>Color</th>
<th>Name</th>
<th>Age</th>
</tr>
</table>
{
tableData.map((entry, index) =>
<tr index={index}>
<td>{entry.color}</td>
<td>{entry.name}</td>
<td>{entry.age}</td>
</tr>
)
}
</div>
)
}
PS: I am not the best at JSX feel free to edit and enhance the render section
I am using an API to fetch some data. When the page loads it fetches some random data, but I want to allow the user to sort the data by clicking a button. I have made a function to sort these data from the API I am using. What I want to do now is: When the button to sort data is clicked, I want the new data to be replaced with the old data.
Here is my current code:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
offset: 0, perPage: 12 // ignore these two
};
}
// The random data that I want to be displayed on page load
receivedData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
const slice = data.slice(this.state.offset, this.state.offset + this.state.perPage) // ignore this
const postData = slice.map(item =>
<tr key={Math.random()}>
<td>{item.province}, {item.country}</td>
<td>{item.stats.confirmed}</td>
<td>{item.stats.deaths}</td>
<td>{item.stats.recovered}</td>
</tr>
)
this.setState({
pageCount: Math.ceil(data.length / this.state.perPage), // ignore this
postData
})
});
}
// The data to be sorted when the "country" on the table head is clicked
sortData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
var someArray = data;
function generateSortFn(prop, reverse) {
return function (a, b) {
if (a[prop] < b[prop])
return reverse ? 1 : -1;
if (a[prop] > b[prop])
return reverse ? -1 : 1;
return 0;
};
}
// someArray.sort(generateSortFn('province', true))
const tableHead = <tr>
<th onClick={() => someArray.sort(generateSortFn('province', true))}>Country</th>
<th>Confirmed Cases</th>
<th>Deaths</th>
<th>Recovered</th>
</tr>
this.setState({
tableHead
})
});
}
componentDidMount() {
this.receivedData()
this.sortData() // This function should be called on the "Country - table head" click
}
render() {
return (
<div>
<table>
<tbody>
{this.state.tableHead}
{this.state.postData}
</tbody>
</table>
</div>
)
}
}
export default Data;
Think a litte bit different. In the componentDidMount get you're Data in some form. Set it with setState only the raw Data not the html. Then resort the data on button click. React rerenders if the state changes automatically
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data
}
}
getData() {
fetchData('url').then(res) {
this.setState({data: res.data})
}
}
componentDidMount() {
this.getData()
}
sort() {
let newSorted = this.state.data.sort //do the sorting here
this.setState({data: newSorted})
}
render() {
return() {
<table>
<tablehead><button onClick={this.sort.bind(this)}/></tablehead>
{this.state.data.map(data => {
return <tablecell>{data.name}</tablecell>
})}
</table>
}
}
}
I am working with a table and I am trying to figure out is there a way to set the default order to ASC when the page loads?
class Orders extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{orders: 'Vanilla', date: '03/15/1990'},
{orders: 'Chocolate', date: '03/15/1989'},
],
sortingOrder: 'ASC'
};
this.sortBy.bind(this);
}
renderTableData() {
return this.state.data.map((data, index) => {
const{orders, date} = data
return (
<tr key={index}>
<td>{orders}</td>
<td>{date}</td>
</tr>
)
})
}
sortBy(sortedKey) {
const data = this.state.data;
let sortingOrder = this.state.sortingOrder;
if(sortingOrder === 'ASC') {
sortingOrder = 'DESC';
data.sort((a,b) => b[sortedKey].localeCompare(a[sortedKey]))
}
else {
sortingOrder = 'ASC';
data.sort((a,b) => a[sortedKey].localeCompare(b[sortedKey]))
}
this.setState({data, sortingOrder })
}
render() {
return (
<table id="orders">
<thead>
<tr className="header">
<th>Order</th>
<th onClick={() => this.sortBy('date')}>Date</th>
</tr>
</thead>
<tbody>
{this.renderTableData()}
</tbody>
</table>
);
}
}
I tried calling this.sortBy() in my render method first, but that gave me an error about too many calls. Any ideas?
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
you can do on componentDidMount for default
componentDidMount(){
this.sortBy('ASC');
}
you should use the comopnentDidMount method
https://reactjs.org/docs/react-component.html#componentdidmount
componentDidMount(){
this.sortBy('date');
}
This question already has answers here:
setState doesn't update the state immediately [duplicate]
(15 answers)
Closed 5 years ago.
From ResultsTable button click I receive desired value to handleClick function, however setState does not set gameId and remains "", where am I wrong? If I console log gameId it is what I want to have. Thank you for any help.
class ResultsTableContainer extends Component {
constructor(props) {
super(props);
this.state = {
tableConfig,
games: [],
gameId: ""
};
}
handleClick = event => {
event.preventDefault();
const gameId = event.target.id;
this.setState({ gameId });
console.log(this.state);
};
currentContent = () => {
if (this.state.gameId !== "") {
return <GameDetailsContainer gameId={this.state.gameId} />;
}
return (
<ResultsTable
tableConfig={this.state.tableConfig}
games={this.state.games}
onButtonClick={this.handleClick}
/>
);
};
render() {
return <div>{this.currentContent()}</div>;
}
}
export default ResultsTableContainer;
ResultsTable.jsx
const ResultsTable = ({ games, tableConfig, onButtonClick }) => (
<Table>
...
<TableBody>
{games.map((game, index) => (
...
<button>
<Link
to={`/games/${game.id}`}
onClick={onButtonClick}
id={game.id}
>
Results
</Link>
</button>
</TableBody>
</Table>
);
export default ResultsTable;
Do not console.log(state) juste after setState.
I think everything is working.
Don't call console right after setState
handleClick = event => {
event.preventDefault();
const gameId = event.target.id;
this.setState({ gameId });
}
Have to display the items via web API. I got the response successfully and displayed the items. But I don't know how to delete the item from the list.
Displayed the delete icon to every item, but I am not able to delete the item from the state. What did I wrong? Please help me out.
import React from 'react';
class App extends React.Component {
constructor() {
super();
this.state = {
posts: []
}
this.UserList = this.UserList.bind(this);
this.setStateHandler = this.setStateHandler.bind(this);
this.setDeleteHandler = this.setDeleteHandler.bind(this);
}
componentDidMount() {
this.UserList();
}
setStateHandler() {
alert(0)
var item = {
"url": "http://www.imdb.com/title/tt4937812/",
"title": "PAMMAL K SAMBANTHAM",
"imdb_id": "tt4937812",
"type": "Film",
"year": "2015"
};
var myArray = this.state.posts;
myArray.push(item)
console.log(myArray)
this.setState({posts: myArray})
};
setDeleteHandler() {
alert("idris")
};
UserList() {
alert(1)
fetch('http://www.theimdbapi.org/api/person?person_id=nm0352032').then(response => {
return response.json();
}).then(data => {
const posts = data.filmography.soundtrack;
this.setState({posts});
});
}
render() {
return (
<div>
<div>
{
this.state.posts.map((item, i) => {
return <Content item={item} key={i}/>
})
}
</div>
<button onClick = {this.setStateHandler}>SET STATE</button>
</div>
);
}
}
class Content extends React.Component {
render() {
return (
<div>
<table>
<tbody>
<tr>
<td>{this.props.item.title}</td>
<td>{this.props.item.type}</td>
<td>{this.props.item.type}</td>
<td><button onClick = {this.setDeleteHandler}>Delete</button></td>
</tr>
</tbody>
</table>
</div>
);
}
}
export default App;
Can anyone please help on this?
Thank you.
Make your setDeleteHandler like this
setDeleteHandler(index) {
return () => {
let posts = [...this.state.posts];
posts.splice(index,1);
this.setState({posts});
}
};
Then pass this function to your content component like this
this.state.posts.map((item, i) => {
return <Content item={item} key={i} deleteHandler={this.setDeleteHandler(i)}/>
})
And then in your content component call this function like this
<td><button onClick = {this.props.deleteHandler}>Delete</button></td>