I'd like for my app to indicate which page I'm currently on. Page numbers get calculated based on the length of an array, and when I move between them, the current page would have a different background. The current page is saved inside the state as well as in local storage.
So I have a CSS class .mark-page {background-color} and I thought I'd just make a ternary inside the button, but it didn't work, however, I'm not sure which parameters to input. Any ideas?
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(allFacts.length / factsPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map((number) => {
return (
<button key={number} id={number} onClick={this.handleClick}>
{number}
</button>
);
});
return <div id="paging">{renderPageNumbers}</div>;
#KavinduVindika sure, posting here because it won't let me edit my post:
class Body extends React.Component {
constructor() {
super()
this.state = {
isLoading: true,
allFacts: [],
currentPage: null,
factsPerPage: 15,
error: null,
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
gets API, saves it into state and sets currentPage to 1 if the page had just
loaded }
handleClick(event) {
this.setState ({
currentPage: localStorage.getItem("currentPage")
})
let currentPage = Number(event.target.id)
localStorage.setItem("currentPage", JSON.stringify(currentPage))
this.setState ({currentPage: currentPage})
}
render() {
const {allFacts, currentPage, factsPerPage } = this.state
const indexOfLastFact = currentPage * factsPerPage
const indexOfFirstFact = indexOfLastFact - factsPerPage
const allFactsSliced = allFacts.slice(indexOfFirstFact, indexOfLastFact)
console.log(allFactsSliced)
const renderAllFacts = this.state.isLoading ?
<div id="loading"><h3 className="fact-div" >Please wait.<br/> The cats are disclosing their secrets.</h3></div> :
allFactsSliced.map((fact, index) => {
return <div className="fact-div" key={index}>
<p className="fact-num">Fact # {(index+1) + (currentPage-1) * factsPerPage}:</p>
<p className="fact-txt">{fact.fact}</p>
</div>
})
hope this is all the important bits
Related
I have some elements inside an array that shares a same state. I need to update only the clicked one in order to add one more item to my shopping cart. How can i do this without changing the others?
My initial state looks like this:
class ShoppingCart extends Component {
constructor() {
super();
this.state = {
isEmpty: true,
cartItems: [],
count: 0,
};
this.getStoredProducts = this.getStoredProducts.bind(this);
this.handleButtonIncrease = this.handleButtonIncrease.bind(this);
}
componentDidMount() {
this.getStoredProducts();
}
handleButtonIncrease() {
this.setState((prevState) => ({
count: prevState.count + 1,
}));
}
getStoredProducts() {
const getFromStorage = JSON.parse(localStorage.getItem('cartItem'));
if (getFromStorage !== null) {
this.setState({
cartItems: getFromStorage,
}, () => {
const { cartItems } = this.state;
if (cartItems.length) {
this.setState({ isEmpty: false });
}
});
}
}
render() {
const { isEmpty, cartItems, count } = this.state;
const emptyMsg = (
<p data-testid="shopping-cart-empty-message">Seu carrinho está vazio</p>
);
return (
<div>
{ isEmpty ? (emptyMsg) : (cartItems.map((item) => (
<ShoppingCartProduct
key={ item.id }
id={ item.id }
count={ count }
cartItems={ item }
handleButtonIncrease={ this.handleButtonIncrease }
/>
)))}
</div>
);
}
}
It seems like this should be ShoppingCartProduct's responsibility. If you remove this count and setCount logic from your ShoppingCart component and create it inside of the ShoppingCartProducts component, each one of the items will have their own count state that can be updated independently.
One other way of seeing this is directly mutating each cartItem, but since you didn't specify their format there's no way of knowing if they're storing any information about quantity so I would go with the first approach.
handleButtonIncrease can accept item.id as parameter so that it can update the state.cartItems.
handleButtonIncrease(itemId) {
const cartItems = this.state.cartItems.map(item => {
return item.id === itemId
? {
// apply changes here for the item with itemId
}
: item
});
this.setState((prevState) => ({
cartItems,
count: prevState.count + 1,
}));
}
After that, update your callback as well:
handleButtonIncrease={ () => this.handleButtonIncrease(item.id) }
I am trying to delete multiple items on click of checkbox using firestore. But, onSnapshot method of firestore is causing issue with the state.
After running the code I can click on checkbox and delete the items, the items get deleted too but I get an error page, "TyperError: this.setState is not a function" in onCollectionUpdate method.
After refreshing the page I can see the items deleted.
Here's my code:
class App extends React.Component {
constructor(props) {
super(props);
this.ref = firebase.firestore().collection('laptops');
this.unsubscribe = null;
this.state = { laptops: [], checkedBoxes: [] };
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.deleteProducts = this.deleteProducts.bind(this);
}
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState = { checkedBoxes: arr };
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState = {
checkedBoxes: items
}
}
}
deleteProducts = () => {
const ids = this.state.checkedBoxes;
ids.forEach((id) => {
const delRef = firebase.firestore().collection('laptops').doc(id);
delRef.delete()
.then(() => { console.log("deleted a laptop") })
.catch(err => console.log("There is some error in updating!"));
})
}
onCollectionUpdate = (querySnapshot) => {
const laptops = [];
querySnapshot.forEach((doc) => {
const { name, price, specifications, image } = doc.data();
laptops.push({
key: doc.id,
name,
price,
specifications,
image
});
});
this.setState({ laptops });
console.log(laptops)
}
componentDidMount = () => {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
getLaptops = () => {
const foundLaptops = this.state.laptops.map((laptop) => {
return (
<div key={laptop.key}>
<Container>
<Card>
<input type="checkbox" className="selectsingle" value="{laptop.key}" checked={this.state.checkedBoxes.find((p) => p.key === laptop.key)} onChange={(e) => this.toggleCheckbox(e, laptop)} />
...carddata
</Card>
</Container>
</div>
);
});
return foundLaptops;
}
render = () => {
return (
<div>
<button type="button" onClick={this.deleteProducts}>Delete Selected Product(s)</button>
<div className="row">
{this.getLaptops()}
</div>
</div>
);
}
}
export default App;
In the toggleCheckbox function you set the this.setState to a object.
You will need to replace that with this.setState({ checkedBoxes: items})
So you use the function instead of setting it to a object
You probably just forgot to bind the onCollectionUpdate so this referes not where you expectit to refer to.
Can you pls also change the this.setState bug you have there as #David mentioned also:
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState({ checkedBoxes: arr });
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState({
checkedBoxes: items
})
}
}
If you already did that pls update your question with the latest code.
I am extremely new to react and am building a simple todo list app. I am trying to edit data from my child component and send it back to my parent. When I am printing console logs the parent state seems to be getting set correctly, but the child elements are not refreshing. Am I doing something conceptually wrong here?
I have tried to share the entire code as I am not sure whether it is correct conceptually. I am new to JS. When the handleSave() and handleComplete() are called i can see correct values getting returned and set to my PArent State, but there is no refresh of the child components.
Below is my Parent class code.
class App extends Component {
state = {
taskList: [
]
};
saveEventHandler = data => {
console.log("I am in saveEventHandler");
var uniqid = Date.now();
const taskList = [...this.state.taskList];
taskList.push({
id: uniqid,
taskDescText: data,
isFinished: false
});
console.log(taskList);
this.setState({'taskList':taskList});
};
deleteEventHandler = (index) => {
const taskList = [...this.state.taskList];
taskList.splice(index,1)
this.setState({'taskList':taskList});
}
editEventHandler = (index,data) => {
var uniqid = Date.now();
console.log("In edit event handler")
console.log(data)
console.log(index)
const taskList = [...this.state.taskList];
taskList[index] = {
id: uniqid,
taskDescText: data,
isFinished: false
}
this.setState({'taskList':taskList});
console.log(this.state.taskList)
}
handleComplete = (index) => {
console.log("In complete event handler")
const taskList = [...this.state.taskList];
const taskDescriptionOnEditIndex = taskList[index]
taskDescriptionOnEditIndex.isFinished = true
taskList[index] = taskDescriptionOnEditIndex
this.setState({'taskList':taskList});
console.log(this.state.taskList)
}
render() {
return (
<div className="App">
<h1>A Basic Task Listing App </h1>
<CreateTask taskDescription={this.saveEventHandler} />
{this.state.taskList.map((task, index) => {
return (
<Task
taskDescText={task.taskDescText}
taskCompleted={task.isFinished}
deleteTask={() => this.deleteEventHandler(index)}
editTask={(editTask) => this.editEventHandler(index,editTask)}
handleComplete={() => this.handleComplete(index)}
editing='false'
/>
);
})}
</div>
);
}
}
export default App;
and my child class code
export default class Task extends React.Component {
state = {
editing : false
}
notCompleted = {color: 'red'}
completed = {color: 'green'}
textInput = React.createRef();
constructor(props) {
super(props);
this.state = props
}
handleEdit = () => {
this.setState({editing:true});
}
handleSave = () => {
this.props.editTask(this.textInput.current.value);
this.setState({editing:false});
};
editingDiv = (<div className = 'DisplayTask'>
<span className='TaskDisplayText' style={!this.props.taskCompleted ? this.notCompleted: this.completed}>{this.props.taskDescText} </span>
<button label='Complete' className='TaskButton' onClick={this.props.handleComplete}> Complete</button>
<button label='Edit' className='TaskButton' onClick={this.handleEdit}> Edit Task</button>
<button label='Delete' className='TaskButton' onClick={this.props.deleteTask}> Delete Task</button>
</div> );
nonEditingDiv = ( <div className = 'DisplayTask'>
<input className='TaskDescEditInput' ref={this.textInput}/>
<button label='Save' className='TaskButton' onClick={this.handleSave} > Save Task</button>
<button label='Delete' className='TaskButton' onClick={this.props.deleteTask}> Delete Task</button>
</div>);
render() {
return (
!this.state.editing ? this.editingDiv : this.nonEditingDiv
)
};
}
Move your editingDiv and nonEditingDiv definitions inside render() method. Since you're defining them as instance variables, they're initialized once, and never get re-rendered again with new prop values.
By moving them to render() method, render() which is called every time when there's a prop update will pick up the new prop values.
Newbie React question here on show hide functionality.
I have a state of 'show' that I set to false:
this.state = {
show: false,
};
Then I use the following function to toggle
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
And my display is
{this.state.show && <xxxxxx> }
This all works fine. However I want to apply the function it to multiple cases (similar to accordion, without the closing of other children. So I change my constructor to
this.state = {
show: [false,false,false,false,false,false]
};
and this to recognise there are 6 different 'shows'.
{this.state.show[0] && <xxxxxx> }
{this.state.show[1] && <xxxxxx> } etc
But where I get stuck is how to account for them in my toggleDiv function. How do I insert the square bracket reference to the index of show (if this is my problem)?
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
Thanks for looking.
First of all I'd suggest you not to rely on current state in setState function, but to use the callback option to be 100% sure that you are addressing to the newest state:
this.setState((prevState) => ({ show: !prevState.show }));
How to deal with multiple elements?
You'll have to pass the index of currently clicked element.
{yourElements.map((elem, i) => <YourElem onClick={this.toggleDiv(i)} />)}
and then inside your toggleDiv function:
toggleDiv = (i) => () => {
this.setState((prevState) => {
const r = [...prevState.show]; // create a copy to avoid state mutation
r[i] = !prevState.show[i];
return {
show: r,
}
}
}
Use an array instead of a single value. In your toggle div function make a copy of the state array make necessary changes and push the entire array back up to state at the end.
This is some simplified code showing the workflow I described above
export default class myClass extends React.Component{
constructor(props){
super(props);
this.state = { show: new Array(2).fill(false) };
}
//you need a index or id to use this method
toggleDiv = (index) => {
var clone = Object.assign( {}, this.state.show ); //ES6 Clones Object
switch(clone[index]){
case false:
clone[index] = true
break;
case true:
clone[index] = false
break;
}
this.setState({ show: clone });
}
render(){
return(
<div>
{ this.state.show[0] && <div> First Div </div> }
{ this.state.show[1] && <div> Second Div </div> }
{ this.state.show[2] && <div> Third Div </div> }
</div>
)
}
}
I currently have something like this:
const socket = require('socket.io-client')('https://example.com');
(....)
// Listen to the channel's messages
socket.on('m', message => {
// this is a Redux action that updates the state
this.props.updateTrades(message);
});
The reducer looks like this:
case actions.UPDATE_TRADES:
return {
...state,
trades: [
...state.trades,
action.trade
]
};
I've tried not using redux and just to the following:
socket.on('m', message => {
this.setState(state => {
if (state.trades.length > 99) {
state.trades.splice(0, 1);
}
return {
trades: [
...state.trades,
message
]
});
});
I don't need to keep increasing my trades array. I'm happy just to keep around 100 items or so...
Socket is sending around 15 messages / second.
My problem is: I can't seem to render the messages in real-time! It just freezes. I guess the stream is just too fast? Any suggestions?
Thanks in advance!
The thing is to do the minimum possible and when the trades change only draw what has change and not all of the elements of the array.A technique that I use is to keep a cache map of already drawn obj, so in the render method I only render the new incoming elements.
Take a look at https://codesandbox.io/s/wq2vq09pr7
class RealTimeList extends React.Component {
constructor(props) {
super(props);
this.cache = [];
}
renderRow(message, key) {
return <div key={key}>Mesage:{key}</div>;
}
renderMessages = () => {
//let newMessages=this,props.newMessage
let newElement = this.renderRow(this.props.message, this.cache.length);
this.cache.push(newElement);
return [...this.cache];
};
render() {
return (
<div>
<div> Smart List</div>
<div className="listcontainer">{this.renderMessages()}</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "hi" };
}
start = () => {
if (this.interval) return;
this.interval = setInterval(this.generateMessage, 200);
};
stop = () => {
clearTimeout(this.interval);
this.interval = null;
};
generateMessage = () => {
var d = new Date();
var n = d.getMilliseconds();
this.setState({ title: n });
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.start}> Start</button>
<button onClick={this.stop}> Stop</button>
<RealTimeList message={this.state.message} />
</div>
);
}
}
The class RealTime List have a cache of elements.Let me know if this helps.
It's probably not a good idea to try to render all of the changes. I think you should try rendering them in batches so you only update once every few seconds, that should help.