I am new to react, I am programming the function onAdd(), when I call that it should add the item to state and then I update the hook with the new item setBooks(state).
const state = {
books: [
{ id: 0, rating: 4, title: "Harry Potter y el cáliz de fuego", image: "libro01.jpg"},
{ id: 1, rating: 3, title: "The shining", image: "libro02.jpg" },
{ id: 2, rating: 5, title: "Código Da Vinci", image: "libro03.jpg" },
{ id: 3, rating: 5, title: "El principito", image: "libro04.jpg" },
{ id: 4, rating: 5, title: "Sobrenatural", image: "libro05.jpg" },
],
copyBooks: []
};
function App() {
const [books, setBooks] = useState(state);
const onAdd = (item)=>{
//add new item to books
console.log('Add: ', item);
const id = state.books[state.books.length-1].id++;
item['id'] = id;
state.books.push(item);
setBooks(state);
}
return (
<div className="App">
<Menu onadd={onAdd}/>
<List items={books.books}/>
</div>
);
}
this works fine internally, I print the books object and it is up to date.
When I try to print the data of the child <List /> component is not printed, until I make a change on the server and it is updated.
I came to the conclusion that the problem is that the List component <List /> is not refreshed.
books refers to hooks and books.books to the data.
I do not know how I can solve it, I know it is something basic but I am starting in this technology.
Your onAdd function is mutating state, meaning React sees the same object reference as before and will not update. To make sure an update happens, copy the array and set the state to the new copy:
const onAdd = (item) => {
//add new item to books
console.log('Add: ', item);
const id = state.books[state.books.length-1].id + 1;
item['id'] = id;
const newBooks = [...state.books, item];
setBooks({ ...state, books: newBooks });
}
Edit: by the way, I might recommend some less confusing terminology when naming variables. The local books variable actually refers to the entire state and then books.books refers to the actual books... that's going to cause mistakes because it's very confusing.
Related
I have array of object as data. The data is displayed on initial page load. When Clear Book button is called with clearBooks() function, I set array to empty (No Data is displayed) and change button value to Show Books
What I am trying to achieve is when Show Books button is clicked I would like to show all objects as before. I though of refreshing page with window.location.reload(); when Show Books is clicked (If there are better solution, open to use them). I need help to achieve this functionality in code
main.js
const clearBooks = () => {
if (buttonTitle === "Clear Books") {
setButtonTitle("Show Books");
}
setBooksData([]);
};
return (
<section className="booklist">
{booksData.map((book, index) => {
return (
<Book key={index} {...book}>
</Book>
);
})}
<div>
<button type="button" onClick={clearBooks}>
{buttonTitle}
</button>
</div>
</section>
);
Data
export const books = [
{
id: 1,
img: "https://m.media/_QL65_.jpg",
author: " A ",
title: "B",
},
{
id: 2,
img: "https://m.media/_QL65_.jpg",
author: " A ",
title: "B",
},
You can achieve this by using useState hook.
const books = [
{
id: 1,
img: "https://m.media/_QL65_.jpg",
author: "A",
title: "B",
},
{
id: 2,
img: "https://m.media/_QL65_.jpg",
author: " A ",
title: "B",
},
]
const [bookList, setBookList] = useState(books);
const clearBooks = () => {
setBookList([]);
}
const showBooks = () => {
setBookList(books);
}
Set bookList to empty when you need to clear book list and to books object when yoou want to show your list books.
You could store the originally fetched data into a separate state and reset the booksData array by setting its value equal to that state on the Show Books click:
const [originalBooksData, setOriginalBooksData] = useState([]);
const [booksData, setBooksData] = useState([]);
const fetchBooks = async () => {
...
// After booksData have been fetched set originalBooksData equal to it
setOriginalBooksData(booksData)
}
const clearBooks = () => {
if (buttonTitle === 'Clear Books') {
// Clear the books
setButtonTitle('Show Books');
setBooksData([]);
} else {
// Reset the books
setBooksData(originalBooksData);
}
};
...
Of course, you will need to set the originalBooksData equal to booksData when the data are loaded.
This will prevent the need to refresh the page when clearing the data.
I want to remove an object from my List array based on its properties value.
Right now I am using findIndex to see if there is an index ID matching my event.target.id.
This is an example of one of the objects in my list array:
{artist: "artist name",
genre: "RnB",
id: 1,
rating: 0,
title: "song name"}
This is my code:
console.log(e.target.id);
const list = this.state.playlist;
list.splice(
list.findIndex(function(i) {
return i.id === e.target.id;
}),
1
);
console.log(list);
}
how ever, instead of it removing the clicked item from the array, it removes the last item, always.
When I do this:
const foundIndex = list.findIndex((i) => i.id === e.target.id)
console.log(foundIndex)
I get -1 back.
What's the problem here?
Use filter for this. Use it to filter out the objects from state where the id of the button doesn't match the id of the current object you're iterating over. filter will create a new array you can then update your state with rather than mutating the existing state (which is bad) which is what is currently happening.
Assuming you're using React here's a working example.
const { Component } = React;
class Example extends Component {
constructor() {
super();
this.state = {
playlist: [
{id: 1, artist: 'Billy Joel'},
{id: 2, artist: 'Madonna'},
{id: 3, artist: 'Miley Cyrus'},
{id: 4, artist: 'Genesis'},
{id: 5, artist: 'Jethro Tull'}
]
};
}
// Get the id from the button (which will be
// a string so coerce it to a number),
// and if it matches the object id
// don't filter it into the new array
// And then update the state with the new filtered array
removeItem = (e) => {
const { playlist } = this.state;
const { id } = e.target.dataset;
const updated = playlist.filter(obj => {
return obj.id !== Number(id);
});
this.setState({ playlist: updated });
}
render() {
const { playlist } = this.state;
return (
<div>
{playlist.map(obj => {
return (
<div>{obj.artist}
<button
data-id={obj.id}
onClick={this.removeItem}
>Remove
</button>
</div>
);
})}
</div>
);
}
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I have a main component, App.js, with the following state.
state = {
cats: [
{ id: '1', name: 'Cat 1'},
{ id: '2', name: 'Cat 2'},
{ id: '3', name: 'Cat 3'},
],
dogs: [
{ id: '1', name: 'Dog 1'},
{ id: '2', name: 'Dog 2'},
],
birds: [
{ id: '1', name: 'Bird 1'},
]
}
Also, in the same component a method (now just consoling the index) and the imported component that list the animals.
Method:
deleteAnimalHandler = (index) => {
console.log(index)
}
JSX or return:
<div>
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.cats} />
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.dogs} />
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.birds} />
</div>
In AnimalList component I´m mapping the props and rendering the lists.
{props.animal.map(({id, name}, index) =>
<div key={id}><div>{name}</div><div onClick={() => props.deleteAnimal(index)}>Delete animal</div></div>
)
}
Every time I click on the div with content "Delete animal" I´m executing the deleteTaskHandler() method and logging in the console the index of that element in the array.
What I´m trying to do of course is not logging into the console, if not, when I click on that div, delete in the particular property of the state the element with the passed index.
So, if I click in Delete animal below Cat 2 I should delete Cat 2 from the cats array. The same for dogs and birds.
I cannot find the logic to let the handler know which is the context or which should be the array that setState() should update.
Anyone can help me? Thanks
You could pass the animal type
<AnimalList deleteAnimal={this.deleteAnimalHandler} animal={this.state.cats} animalType='cats' />
And add to the click
onClick={() => props.deleteAnimal(index, this.props.animalType)
And implement the deleteAnimalHandler
deleteAnimalHandler = (index, animalType) => {
let newAnimals = this.state.animals[animalType].filter((x, i) => i != index)
let newState = {}
newState[animalType] = newAnimals
this.setState(newState)
}
You need to pass category of the animal too.
deleteAnimalHandler = (index,category) => {
let temp = JSON.parse(JSON.stringify(this.state))
temp[category] = temp[category].filter(({id})=> id !== index )
// you can set state now
}
Here you can pass the name of category
{props.animal.map(({id, name}, index) =>
<div key={id}><div>{name}</div><div onClick={() => props.deleteAnimal(index,this.props.category)}>Delete animal</div></div>
)
}
Here's a piece of function just return props to element named box in React.
In this MovieDatabase is a JSON file which contains titles of different movies. I want to only that movies whose name includes "Star Wars".
const showMovies = MovieDatabase.map((movie) =>{
const aa = movie.title.contains("Star Wars")
return (
<Box movieName = {aa} />
);})
I think it'd be better to filter separate from the component.
Here's a way:
// assuming the DB is something like this based on your .map example
const MovieDatabase = [
{ title: 'Star Wars: II', id: 0 },
{ title: 'Star Wars: I', id: 1 },
{ title: 'Star Wars: IV', id: 2 },
{ title: 'Moon', id: 3 }
]
// array of star wars movies
const starWars = MovieDatabase.filter(movie => movie.title.includes('Star Wars'))
// component to render movies ( doesn't care about anything but rendering )
const MovieBoxes = movies => movies.map(movie => (
<Box key={movie.id} movieName={movie.title} />
)
You could further improve it by wrapping the filter in a function that takes a keyword and passes it to includes so you can find any movie.
You can use array.filter()
const array = [{
title: 'Star Wars',
duration: 107
}, {
title: 'Star Wars',
duration: 103
}, {
title: 'Lord Of The Rings',
duration: 500
}]
const filterArray = array.filter(film => film.title === 'Star Wars')
console.log(filterArray)
You can also return null in your map() as your filter without needing to explicitely use Array#filter() since React will not render that
const showMovies = MovieDatabase.map((movie) =>{
const {title} = movie
return title.contains("Star Wars") ? <Box movieName = {title} /> : null;
})
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.