I have an array of objects like this
const Guide = [
{
id: 1,
title: 'Dashboard',
content: 'The dashboard is your main homepage. It will display a feed of looks...'
},
{
id: 2,
title: 'Discover',
content: 'Discover allows you to find new looks, what is trending and search for looks, brands and users'
},
{
id: 3,
title: "Upload you look, style guide and more "
},
{
id: 4,
title: "Upload you look, style guide and more "
},
{
id: 5,
title: "Upload you look, style guide and more "
}
]
I want to be able to click a button and go to the display the data of the next object up to the last one. Currently when I click the button it just changes to the second object "Discover" and stops there, how can I ensure that it goes through with this functionality. Please excuse my grammar.
This is my state when the component mounts
componentWillMount(){
this.setState({
index: Guide[0]
})
}
The initial state index is = 0, And this is my function to go to the next object
moveNext = () => {
let i = Guide.indexOf(Guide[0])
if(i >= 0 && i < Guide.length)
this.setState({
index: Guide[i + 1]
})
}
Change this
moveNext = () => {
let i = Guide.indexOf(Guide[0])
if(i >= 0 && i < Guide.length)
this.setState({
index: Guide[i + 1]
})
}
To this
moveNext = () => {
let i = Guide.indexOf(this.state.index)
if(i >= 0 && i < Guide.length)
this.setState({
index: Guide[i + 1]
})
}
Explanation:
This let i = Guide.indexOf(Guide[0]) makes you keep setting the i value to 0, thats why when you click next you keep getting the second data.
By change it to this let i = Guide.indexOf(this.state.index) you will set the i value to the current index, not 0.
I hope my explanation is understandable :)
Your state should contain the minimal information required, which in this case is the index of the item in the array. You could also use the id, but that would require extra work and it looks like they map directly onto the indices anyway.
const info = [{
title: 'Dashboard',
content: 'The dashboard is your main homepage. It will display a feed of looks...'
},
{
title: 'Discover',
content: 'Discover allows you to find new looks, what is trending and search for looks, brands and users'
},
{
title: "Upload you look, style guide and more "
}
];
class Guide extends React.Component {
constructor() {
super();
this.state = {
index: 0
};
}
goToNext = () => {
this.setState({ index: (this.state.index + 1) % info.length });
};
render() {
const item = info[this.state.index];
return (<div>
<h2>{item.title}</h2>
<p>{item.content}</p>
<button onClick={this.goToNext}>next</button>
</div>);
}
}
ReactDOM.render(<Guide/>, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
I'm not sure what you're trying to do with "let i = Guide.indexOf(Guide[0])".
Try incrementing the index of the state by 1, like this:
componentWillMount() {
this.state = { index: 0 };
}
moveNext = () => {
let i = this.state.index;
if(i >= 0 && i < Guide.length) {
this.setState({
index: i + 1
});
}
}
In the render method, use Guide[this.state.index] to get the data of the current index.
Related
During the React.js course I'm doing, I was tasked with making a simple fortune-teller app. Theoretically, everything works as planned, but I did the task differently than the tutor. Instead of a simple fortune-telling table, I've created an array of objects, each with its id and 'omen'. The problem arose when after adding a new 'omen' an alert should be displayed that gives the current content of 'omens' in state. Only the previous values appear, without the added value. I will be grateful for the hints. In the original design, this problem does not occur, although it is very similar.
class Draw extends React.Component {
state = {
index: "",
value: "",
omens: [
{ id: 1, omen: "Hard work pays off" },
{ id: 2, omen: "You will be rich" },
{ id: 3, omen: "Be kind to others" },
],
};
handleDrawOmen = () => {
const index = Math.floor(Math.random() * this.state.omens.length + 1);
this.setState({
index: index,
});
};
showOmen = () => {
let omens = this.state.omens;
omens = omens.filter((omen) => omen.id === this.state.index);
return omens.map((omen) => (
<h1 id={omen.id} key={omen.id}>
{omen.omen}
</h1>
));
};
handleInputChange = (e) => {
this.setState({
value: e.target.value,
});
};
handleAddOmen = () => {
if (this.state.value === "") {
return alert("Enter some omen!");
}
const omens = this.state.omens.concat({
id: this.state.omens.length + 1,
omen: this.state.value,
});
this.setState({
omens,
value: "",
});
console.log(this.state.omens);
alert(
`Omen added. Actual omens: ${this.state.omens.map(
(omen) => omen.omen
)}`
);
};
render() {
return (
<div>
<button onClick={this.handleDrawOmen}>Show omen</button>
<br />
<input
placeholder="Write your own omen..."
value={this.state.value}
onChange={this.handleInputChange}
/>
<button onClick={this.handleAddOmen}>Add omen</button>
{this.showOmen()}
</div>
);
}
}
ReactDOM.render(<Draw />, document.getElementById("root"));
The state object is immutable. So you need to create your new array and apply it afterwards:
const omens = [
...this.state.omens,
{
id: this.state.omens.length + 1,
omen: this.state.value,
}
]
also setState is async so you need to wait until it finished:
this.setState({
omens,
value: "",
}, () => {
alert(
`Omen added. Actual omens: ${this.state.omens.map(
(omen) => omen.omen
)}`
)
https://reactjs.org/docs/react-component.html#setstate
So I have an e-commerce webpage but for some reason, after adding an item past the first time to a cart it starts to double the value of units in cart as well as double my Toasts. I am wondering what I am doing wrong here. My initial State is 0 for cartItem. Any help will be much appreciated.
here is what I am working with:
Example of cartItem Object:
[{
description: "...."
featured: false
id: "6u7pLcaGApuGtiNAf6zLMf"
image: ["//images.ctfassets.net/f1r553pes4gs/17rq7BQ76Q7ouq…010636019e9b3cbc3a10/il_794xN.2378509691_kaep.jpg"]
inCart: false
ingredients: (2) ["Distilled Water", "99.99% Fine Silver Rods"]
price: 20
productName: "Quintessence Colloidal Silver (4 fl oz)"
slug: "quintessence-colloidal-silver-4oz"
units: 9
}]
Shorted Version of Code:
export class StoreProvider extends Component {
//Initialized State ready for API Data
state = {
products: [],
featuredProducts: [],
sortedProducts: [],
price: 0,
maxPrice: 0,
minPrice: 0,
units: 0,
loading: true,
//FIXME CART
cartItem: []
}
handleAddToCart = (e, products) => {
this.setState(state => {
const cartItem = state.cartItem;
let productsAlreadyInCart = false;
cartItem.forEach(cp => {
if (cp.id === products.id) {
//Have tried ++
cp.units+= 1;
productsAlreadyInCart = true;
this.successfullCartToast()
}
});
if (!productsAlreadyInCart) {
cartItem.push({ ...products});
}
localStorage.setItem('cartItem', JSON.stringify(cartItem));
return { cartItem: cartItem };
});
}
}
//Button is in seperate component
<button
className="btn-primary rounded col-sm-6 col-lg-12 align-self-center ml-1 p-2"
onClick={(e) => handleAddToCart(e, product)}>
+ Cart
</button>
Issue
You are mutating existing state and toasting every cart item you check.
Solution
First search the cart array if item is already contained. If it is already contained then simply map the cart and update the appropriate index, otherwise, append to a shallowly copied cart item array.
Also, setState should be a pure function, so don't do side-effects like setting localStorage inside the setState functional update, instead use the setState callback, or preferably, the componentDidUpdate lifecycle function. Or you can just set localStorage with the same value you're updating state with.
handleAddToCart = (e, products) => {
const itemFoundIndex = this.state.cartItem.findIndex(
cp => cp.id === products.id
);
let cartItem;
if (itemFoundIndex !== -1) {
this.successfullCartToast();
// map cart item array and update item at found index
cartItem = this.state.cartItem.map((item, i) =>
i === itemFoundIndex ? { ...item, units: item.units + 1 } : item
);
} else {
// shallow copy into new array, append new item
cartItem = [...this.state.cartItem, products];
}
localStorage.setItem("cartItem", JSON.stringify(cartItem));
this.setState({ cartItem });
};
So, cartItem is an Object, looking something like:
[{'id': 123, 'units': 2}, {'id': 345, 'units': 1}, ...] ?
One thing to try would be to make a deep copy of the Object, so it doesn't changes while updating:
handleAddToCart = (e, products) => {
this.setState(state => {
//const cartItem = state.cartItem;
// make deep copy, so state doesn't change while manipulating:
const cartItem = JSON.parse(JSON.stringify( state.cartItem ));
let productsAlreadyInCart = false;
cartItem.forEach(cp => {
if (cp.id === products.id) {
//Have tried ++
cp.units += 1;
productsAlreadyInCart = true;
this.successfullCartToast()
}
});
if (!productsAlreadyInCart) {
cartItem.push({ ...products});
}
localStorage.setItem('cartItem', JSON.stringify(cartItem));
return { cartItem: cartItem };
});
}
}
I'm having trouble updating a list of elements using React, when I run the code below and click on a 'star' element, react updates ALL the elements in this.state.stars instead of just the element at the given index:
class Ratings extends React.Component {
constructor(props) {
super(props);
let starArr = new Array(parseInt(this.props.numStars, 10)).fill({
icon: "*",
selected: false
});
this.state = {
stars: starArr
};
this.selectStar = this.selectStar.bind(this);
}
selectStar(ind) {
this.setState({
stars: this.state.stars.map((star, index) => {
if (index === ind) star.selected = !star.selected;
return star;
})
});
}
makeStars() {
return this.state.stars.map((star, ind) => (
<span
className={star.selected ? "star selected" : "star"}
onClick={() => this.selectStar(ind)}
key={ind}
>
{star.icon}
</span>
));
}
render() {
return (
<div className="star-container">
<span>{this.makeStars()}</span>
</div>
);
}
}
Can anyone point me in the right direction here? Not sure why this is happening!
Your problem is in how you're instantiating your Array:
let starArr = new Array(parseInt(this.props.numStars, 10)).fill({
icon: "*",
selected: false
});
What that line is doing is filling each item in the array with the same object. Not objects all with the same values, but a reference to the same object. Then, since you're mutating that object in your click handler, each item in the Array changes because they're all a reference to that same object.
It's better to do a non-mutating update, like this:
this.setState({
stars: this.state.stars.map((star, index) =>
(index === ind)
? { ...star, selected: !star.selected }
: star
)
});
This will instead create a copy of the object at the Array index except with the selected property toggled.
So my Reducer is:
const initialState = {
1: {
id: '1',
user: 'User1',
text: 'Dummy Text id1',
SomeFiled: 'SomeValue',
},
2: {
id: '2',
user: 'User1',
text: 'Dummy Text id2',
SomeFiled: 'SomeValue',
},
3: {
id: '3',
user: 'User1',
text: 'Dummy Text id3',
SomeFiled: 'SomeValue',
},
4: {
id: '4',
user: 'User1',
text: 'Dummy Text id4',
SomeFiled: 'SomeValue',
},
5: {
id: '5',
user: 'User1',
text: 'Dummy Text id5',
SomeFiled: 'SomeValue',
}
}
I've mapStateToProps with prop users and able to show the data:
const renData = Object.keys(this.props.users).map((key, idx) => {
let user = this.props.users[key]
return(
<View key={idx} style={styles.myStyle}>
<Text style={styles.myStyleText}>
{ user.id } - { user.user } - { user.Text }
</Text>
</View>
)
});
I want to show only 2 objects from the Reducer. So the first (id: '1') and second (id: '2') but not based on id, only the first 2. And then have a Button which onPress will load more 2 values. And If there are any more values, the button will show, else not show. Not worried about the display of the Button for now. I want to know how to apply limit in rendering values from a reducer.
Many thanks.
You have to use slice method.
The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
let size=2;
const renData = Object.keys(this.props.users).slice(0,size).map((key, idx) => {
let user = this.props.users[key]
return(
<View key={idx} style={styles.myStyle}>
<Text style={styles.myStyleText}>
{ user.id } - { user.user } - { user.Text }
</Text>
</View>
)
});
You can declare an object in the state of the component:
this.state={
data:{
count:count,
dataArray:array
}
}
and use setState method in order to bind values.
this.setState({
data: {
count:newCount
dataArray: newArray
}
});
The same scenario what are you are expecting with pure JS. Using same Array#slice logic as #MayankShukla said.
var count = 0;
const data = [1,2,3,4,5,6,7,8,9,10,11];
function renderMore(){
count+= 2;
if(count > data.length) {
count = data.length;
document.getElementById("button").disabled = true;
}
let renData = data.slice(0,count);
console.log(renData)
}
<button id="button" onclick="renderMore()">Show more</button>
Hope this helps :)
Maintain a state variable inside component, that will hold the count of items that you want to render, initial value of that variable will be 2.
Then use #array.slice to get the part of data, and run #array.map on it, To load more items, update the count of that variable.
Write it like this:
const renData = Object.keys(this.props.users).slice(0, 2).map((key, idx) => {
......
}
Note: In place of two use that variable.
I'm studying react.js.
How to correctly add class 'use' to the element where the click occurs? From other elements it needs to be removed.
How to get rid of the index, but be able to handle and dispose of the items?
var DB = [
{
name: 'Имя 1', url: 'http://localhost:1', use: true
},
{
name: 'Имя 2', url: 'http://localhost:2', use: false
},
{
name: 'Имя 3', url: 'http://localhost:3', use: false
}
];
class SideBarEl extends React.Component {
hoverLi(t){
if(t.target.id !== ''){
for (var i = 0; i < DB.length; i++){
if(t.target.id == i){
DB[i].use = true;
} else {
DB[i].use = false;
}
}
}
}
render(){
var newsTemplate = DB.map(function(item, index) {
return (
<li key={ index } id={ index } onClick={ this.hoverLi.bind(this)} className={ item.use ? 'use' : '' }>
{ item.name }
<span>
{ item.url }
</span>
</li>
)
}, this);
return(
<ul>{newsTemplate}</ul>
)
}
}
1 Set this.state
You need to use React state to handle such things and rerender when action occurs. If you just use a variable, React doesn't know when something should be rerendered.
this.state = {
links: [
{
name: "Имя 1",
url: "http://localhost:1",
use: true
},
{
name: "Имя 2",
url: "http://localhost:2",
use: false
},
{
name: "Имя 3",
url: "http://localhost:3",
use: false
}
]
};
Read more about state on https://facebook.github.io/react/docs/state-and-lifecycle.html
2 Update state by using onClick
handleClick(item) {
this.setState(prevState => ({
links: prevState.links.map(link => {
link.use = link === item;
return link;
})
}));
}
render() {
// rest of code...
<li
key={item.url}
id={index}
onClick={() => this.handleClick(item)}
className={item.use ? "use" : ""}
>
// rest of code...
}
For only 3 links it's okay to have such non-optimized code. If you would like to apply this for big collection of links (hundreds or thousands of links), it will require a bit more work but probably it's out of you question's scope.
3 Demo
https://codesandbox.io/s/mQoomVOmA
If you click on a link, it will be red and rest will be black because I added this small CSS .use { color: red; }
Have fun and happy coding.