I'd like to update the state and display the current state in a React Fragment inside a method. I'm updating the state in reserve method and I'd like to display the new value on a button click. I was trying to do that in the span in fetchData() method but when I click the button, I don't see the updated value. Below is the screen shot of the application and code.
class BooksComponent extends Component{
constructor(props){
super(props)
this.state ={
booksData: [],
offset: 0,
perPage: 3,
currentPage: 0,
}
this.reserve = this.reserve.bind(this)
this.fetchData = this.fetchData.bind(this)
}
fetchData(){
axios.get('/library')
.then(res => {
const booksData = res.data
const slice = booksData.slice(this.state.offset, this.state.offset + this.state.perPage)
const books = slice.map(book =>
<React.Fragment key={book.id}>
<p>{book.id} - {book.title} - {book.author}</p>
<button onClick={() => this.reserve(book.id)}>Reserve {book.quantity}</button>
<span>{this.state.booksData.quantity}</span>
</React.Fragment>)
this.setState({
pageCount: Math.ceil(booksData.length / this.state.perPage),
books })
})
}
handlePageClick = (e) => {
const selectedPage = e.selected;
const offset = selectedPage * this.state.perPage;
this.setState({
currentPage: selectedPage,
offset: offset
}, () => {
this.fetchData()
});
};
componentDidMount() {
this.fetchData()
}
render() {
return (
<div className="App">
{this.state.books}
<ReactPaginate
previousLabel={"prev"}
nextLabel={"next"}
breakLabel={"..."}
breakClassName={"break-me"}
pageCount={this.state.pageCount}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
onPageChange={this.handlePageClick}
containerClassName={"pagination"}
subContainerClassName={"pages pagination"}
activeClassName={"active"}/>
</div>
)
}
reserve(id) {
console.log("clicked")
this.setState({
booksData: this.state.booksData.map(item => {
if (item.id === id) {
return { ...item, quantity: (item.quantity - 1) >= 0 ? (item.quantity - 1) : 0};
} else {
return item;
}
})
})
}
}
export default BooksComponent
Don't store jsx in your state, store the data and let React handle the render, so, just do this in your fetchData function
this.setState({
pageCount: Math.ceil(booksData.length / this.state.perPage),
books
})
and in your render
<div className="App">
{this.state.books.map(book =>
<React.Fragment key={book.id}>
<p>{book.id} - {book.title} - {book.author}</p>
<button onClick={() => this.reserve(book.id)}>Reserve {book.quantity}</button>
<span>{this.state.booksData.quantity}</span>
</React.Fragment>
)}
<ReactPaginate...
</div>
This will re-render when the state changes and will display the correct value
Related
My default value is null (let activestatus = "";), but I want it to change on click to be:
let activestatus = "?IsActive=0";
I am getting value on click (as seen in console), but the value is not passed in "let activestatus".
class App extends Component {
state = {
reservations: [],
};
componentWillMount() {
let activestatus = "";
axios
.get("https://localhost:44307/api/GetReservations/" + `${activestatus}`)
.then((response) => {
this.setState({
reservations: response.data,
});
});
}
showActive = (e) => {
e.preventDefault();
console.log(e.target.value);
this.activestatus = e.target.value;
};
render() {
let reservations = this.state.reservations.map((reservation) => {
return (
<tr>
<td>{reservation.Id}</td>
</tr>
);
});
return (
<div className="App container">
<Button
class="activity-button"
value={"?IsActive=0"}
id="active"
onClick={this.showActive}
>
Can you try to have activeStatus as part of your state? Also if you want to refresh the data from the api based on this field, then should probably use componentDidUpdate that runs on state changes.
class App extends Component {
state = {
reservations: [],
activestatus: ""
};
componentWillMount() {
axios
.get("https://localhost:44307/api/GetReservations/" + `${activestatus}`)
.then((response) => {
this.setState({
reservations: response.data,
});
});
}
showActive = (e) => {
e.preventDefault();
console.log(e.target.value);
this.setState({ activestatus: e.target.value });
};
render() {
let reservations = this.state.reservations.map((reservation) => {
return (
<tr>
<td>{reservation.Id}</td>
</tr>
);
});
return (
<div className="App container">
<Button
class="activity-button"
value={"?IsActive=0"}
id="active"
onClick={this.showActive}
>`
Thanks guys, both were helpful.
Solution:
class App extends Component {
state = {
reservations: [],
activestatus: "",
};
componentDidUpdate() {
axios
.get(
"https://localhost:44307/api/GetReservations/" +
`${this.state.activestatus}`
)
.then((response) => {
this.setState({
reservations: response.data,
});
});
}
}
showActive = (e) => {
e.preventDefault();
console.log(e.target.value);
this.setState({ activestatus: e.target.value });
};
render() {
let reservations = this.state.reservations.map((reservation) => {
return (
<tr>
<td>{reservation.Id}</td>
</tr>
);
});
return (
<div className="App container">
<Button
class="activity-button"
value={"?IsActive=0"}
id="active"
onClick={this.showActive}
>`
i am trying to do a simple toDo app with react. I couldnt do how to delete an element in list. Here my code; first state:
class AppForm extends Component {
constructor(props) {
super(props);
this.state = { items: [] , text:''};
this.onChangeHandler=this.onChangeHandler.bind(this)
this.submitHandler=this.submitHandler.bind(this)
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
};
this.setState(state => ({
items: state.items.concat(arrayItem),
text: ''
}));
}
here the problem area. I also tried splice but couldnt.
deleteItem=(index)=>{
let todos= this.state.items.filter(todo => index !== todo.key)
this.setState({
items : todos
})
}
then rendering..
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input type="text" onChange={this.onChangeHandler} value={this.state.text}/>
</form>
<ul>
{this.state.items.map((item,index) =>{
return (
<li key={index}> {item.text}
<p onClick={this.deleteItem.bind(this,index)}> X </p>
</li>
)
})}
</ul>
</div>
);
}
}
export default AppForm;
Splice is the answer.
First, I create a copy of your state array. Then splice it using the index clicked. Then set setState with the spliced array.
deleteItem=(index)=>{
let todos= [...this.state.items]
todos.splice(index, 1)
this.setState({
items : todos
})
}
deleteItem = (index) => {
this.setState(({items}) => {
return {items: [...items.filter(todo => index !== todo.key)]};
})
}
First of all you're not setting the key anywhere when you are inserting in array. It is not at all recommended to use index as key in array. It should be unique.
const arrayItem = {
text: this.state.text,
id: uuid()
};
So I've added the uuid and compared with the id of the element.
codesandbox
uuid
// UNIQUE KEY GENERATOR
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export default uuidv4;
React component
import React, { Component } from "react";
import uuid from "./uuid";
import "./styles.css";
class App extends Component {
constructor(props) {
super(props);
this.state = { items: [], text: "" };
this.onChangeHandler = this.onChangeHandler.bind(this);
this.submitHandler = this.submitHandler.bind(this);
}
//setting input value to the text in state
onChangeHandler = (e) => {
this.setState({
text: e.target.value
});
};
//pushing text item of the state to the items
submitHandler = (e) => {
e.preventDefault();
const arrayItem = {
text: this.state.text,
id: uuid()
};
this.setState((state) => ({
items: state.items.concat(arrayItem),
text: ""
}));
};
deleteItem = (key) => {
let todos = this.state.items.filter((todo) => key !== todo.id);
this.setState({
items: todos
});
};
render() {
return (
<div>
<h1>toDo App</h1>
<form onSubmit={this.submitHandler}>
<label>Type the task you want to do!</label>
<input
type="text"
onChange={this.onChangeHandler}
value={this.state.text}
/>
</form>
<ul>
{this.state.items.map((item) => {
return (
<li key={item.id}>
{item.text}
<p onClick={() => this.deleteItem(item.id)}> X </p>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
I am fetching information about stocks in componentDidUpdate in React. The information that I want to fetch renders but after a few seconds the information reverts back to 0. I don't know why it is happening and I have tried to fetch it in componentDidMount first but the same thing keeps happening. I don't know how to go about solving this problem. Can someone point me in the right direction?
constructor(props) {
super(props);
this.state = {
userInput: '',
stockSymbol: [],
marketData: [],
isLoaded: false,
symbol1: [],
};
}
typeSymbol = e => {
this.setState({
userInput: e.target.value.toUpperCase(),
});
};
componentDidMount() {
const { userInput } = this.state;
const urls = [
`https://api.iextrading.com/1.0/ref-data/symbols`,
`https://api.iextrading.com/1.0/tops`,
];
let requests = urls.map(url => fetch(url));
Promise.all(requests)
.then(responses => {
return Promise.all(responses.map(response => response.json()));
})
.then(responses => {
this.setState({
stockSymbol: responses[0],
marketData: responses[1],
});
});
}
componentDidUpdate(prevProps, prevState, snapShot) {
const { userInput, symbol1 } = this.state;
if (prevState.userInput !== userInput) {
fetch(
`https://finnhub.io/api/v1/quote?symbol=${userInput}&token=brjo6knrh5r9g3ot7150`,
)
.then(res => res.json())
.then(responses => this.setState({ symbol1: responses }));
}
}
render() {
const { stockSymbol, userInput, marketData, symbol1 } = this.state;
const filteredSymbols = stockSymbol.filter(sym => sym.symbol === userInput);
const foundMarket = marketData.find(market => market.symbol === userInput);
const objData = () => {
for (const stockData in symbol1) {
console.log(`${stockData}: ${symbol1[stockData]}`);
}
};
objData();
const clock = new Date().toLocaleString();
return (
<div className="enterstock">
<div className="fields">
<span className="clock">{clock}</span>
<h1 className="title">Enter Stock Symbol</h1>
<input
type="text"
className="symfields"
name="symbolname"
onChange={this.typeSymbol}
/>
</div>
{filteredSymbols.map((stock, i) => {
return (
<div className="stockings">
<div className="named">
<h2 className="symbol">{this.state.userInput}</h2>
<h2 className=" name" key={i}>
{stock.name}
</h2>
</div>
{
<h2 className="stocked price" key={i}>
Price: {symbol1.c}
</h2>
}
</div>
);
})}
,
</div>
);
}
I am working with an API such that on clicking button "show more" there's some counter which increases itself by 25 and display next content:
constructor(props){
this.state = { counter: 0}}
showMore = () => {
axios.get(some_random_link/append/${this.state.counter + 25}/0
}.then(res => {
this.setState({ counter: this.state.counter + 25 });
});
render(){
return(
<div>
<button onClick={this.showMore}>show more</button>
</div>
Expected:
some_random_link/append/25/0
some_random_link/append/50/0
some_random_link/append/75/0
Actual:
some_random_link/append/25/0
some_random_link/append/25/0
some_random_link/append/25/0
setState is an async process, hence when you want to update a state by using the previous one then do it this way
class Counter {
constructor(props) {
this.state = { counter: 0 };
}
_showMore = () => {
const { counter } = this.state;
axios.get(`some_random_link/append/${counter + 25}/0`).then(res => {
this.setState(prevState => ({ counter: prevState.counter + 25 }));
});
};
render() {
return (
<div>
<button onClick={this._showMore}>show more</button>
</div>
);
}
}
Call axios call after setState()
class Counter {
constructor(props) {
this.state = { counter: 0 };
}
showMore = () => {
this.setState(
({ counter }) => ({ counter: counter + 25 }),
() => {
axios.get(`some_random_link/${this.state.counter}/0`); // this.state.counter already updated
}
);
};
render() {
return (
<div>
<button onClick={this.showMore}>show more</button>
</div>
);
}
}
this.setState() is asynchronous! so instead of this.setState({})
change it to
this.setState(prev => counter : prev.counter + 25)
For more details check this link:
Beware: React setState is asynchronous!
class App extends React.Component {
constructor(props){
super(props)
this.state={counter:0};
}
showMore = () => {
axios
.get(`/fake/fake/fake/${this.state.counter}`)
.then(() => {
console.log("fake data");
})
.catch(() => {
console.log(this.state.counter)
this.setState(prevState => ({ counter: prevState.counter + 25 }));
});
};
render(){
return (
<span style={{background:"green",color:"#fff",padding:"15px",cursor:"pointer"}}onClick={this.showMore}>PLZ CLICK ME</span>
)
}
}
ReactDOM.render(<App />, document.querySelector('#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>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="root"></div>
I'm probably missing something very simple here, but I can't get my code to run properly. I'm trying to display a nested unordered list of referenceListItems for every referenceList. The main question I guess is how do I pass the state variable referenceListItems into the child component ReferenceListItems?
const ReferencePage = () => (
<div>
<h1>Reference</h1>
<Reference />
</div>
);
class ReferenceBase extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
referenceLists: [],
referenceListItems: [],
};
}
componentDidMount() {
this.onListenForReferenceLists();
this.onListenForReferenceListItems();
}
onListenForReferenceLists() {
this.setState({ loading: true });
this.unsubscribeReferenceLists = this.props.firebase
.referenceLists()
.onSnapshot(snapshot => {
if (snapshot.size) {
let referenceLists = [];
snapshot.forEach(doc =>
referenceLists.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
referenceLists: referenceLists,
loading: false
});
} else {
this.setState({ referenceLists: null, loading: false });
}
});
}
onListenForReferenceListItems() {
this.unsubscribeReferenceListsItems = this.props.firebase
.referenceListItems()
.onSnapshot(snapshot => {
if (snapshot.size) {
let referenceListItems = [];
snapshot.forEach(doc =>
referenceListItems.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
referenceListItems: referenceListItems,
loading: false
});
} else {
this.setState({ referenceListItems: null, loading: false });
}
});
}
componentWillUnmount() {
this.unsubscribeReferenceLists();
this.unsubscribeReferenceListsItems();
}
render() {
const { referenceLists, referenceListItems, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{referenceLists ? (
<ReferenceLists referenceLists={referenceLists} />
):(
<div>There are no reference items ...</div>
)}
</div>
);
}
}
const Reference = withFirebase(ReferenceBase);
const ReferenceLists = ({ referenceLists }) => (
<ul className="reference-lists">
{referenceLists.map( referenceList => (
<ReferenceList key={referenceList.uid} referenceList={referenceList} />
))}
</ul>
);
const ReferenceList = ({ referenceList }) => (
<li className="reference">
<strong>{referenceList.userId}</strong> {referenceList.name}
<ReferenceListItems />
</li>
);
const ReferenceListItems =({ referenceListItems }) => (
<ul className="reference-list-items">
{referenceListItems.map( referenceListItem => (
<ReferenceListItem key={referenceListItem.uid} referenceListItem={referenceListItem} />
))}
</ul>
);
const ReferenceListItem = ({ referenceListItem }) => (
<li className="reference-list-item">
<strong>{referenceListItem.userId}</strong> {referenceListItem.name}
</li>
);
You do not explicitly use your ReferenceListItems inside the parent ReferenceBase component. So you'll just have to pass it down as a property throughout the component tree.
render() {
const { referenceLists, referenceListItems, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{referenceLists ? (
<ReferenceLists referenceLists={referenceLists} referenceListItems={referenceListItems} />
):(
<div>There are no reference items ...</div>
)}
</div>
);
ReferenceLists
const ReferenceLists = ({ referenceLists, referenceListItems }) => (
<ul className="reference-lists">
{referenceLists.map( referenceList => (
<ReferenceList key={referenceList.uid} referenceList={referenceList} referenceListItems={referenceListItems} />
))}
</ul>
ReferenceList
const ReferenceList = ({ referenceList, referenceListItems }) => (
<li className="reference">
<strong>{referenceList.userId}</strong> {referenceList.name}
<ReferenceListItems referenceListItems={referenceListItems}/>
</li>
);
By redeclaring referenceList
let referenceLists = [];
it never gets set in your class. you either need to return the referenceLists inside the closure or set the class level variable in your callback
this.referenceLists.push({ ...doc.data(), uid: doc.id })