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.
Related
I want to filter items by filter method and I did it but it doesn't work in UI but
when I log it inside console it's working properly
I don't know where is the problem I put 2 images
Explanation of this code:
Looping inside currencies_info by map method and show them when I click on it and this completely working then I want filter the list when user enter input inside it I use filter method and this completely working in console not in UI
import React, { useState } from "react";
// Artificial object about currencies information
let currencies_info = [
{
id: 1,
name: "بیت کوین (bitcoin)",
icon: "images/crypto-logos/bitcoin.png",
world_price: 39309.13,
website_price: "3000",
balance: 0,
in_tomans: 0
},
{
id: 2,
name: "اتریوم (ethereum)",
icon: "images/crypto-logos/ethereum.png",
world_price: 39309.13,
website_price: "90",
balance: 0,
in_tomans: 0
},
{
id: 3,
name: "تتر (tether)",
icon: "images/crypto-logos/tether.png",
world_price: 39309.13,
website_price: "5",
balance: 0,
in_tomans: 0
},
{
id: 4,
name: "دوج کوین (dogecoin)",
icon: "images/crypto-logos/dogecoin.png",
world_price: 39309.13,
website_price: "1000000",
balance: 0,
in_tomans: 0
},
{
id: 5,
name: "ریپل (ripple)",
icon: "images/crypto-logos/xrp.png",
world_price: 39309.13,
website_price: "1,108",
balance: 0,
in_tomans: 0
}
];
export default function Buy() {
// States
let [api_data, set_api_data] = useState(currencies_info);
const [currency_icon, set_currency_icon] = useState("");
const [currency_name, set_currency_name] = useState("");
const [currency_price, set_currency_price] = useState(0);
const [dropdown, set_drop_down] = useState(false);
let [search_filter, set_search_filter] = useState("");
// States functions
// this function just toggle dropdown list
const toggle_dropdown = () => {
dropdown ? set_drop_down(false) : set_drop_down(true);
};
// this function shows all currencies inside dropdown list and when click on each item replace
// the currency info and hide dropdown list
const fetch_currency = (e) => {
set_drop_down(false);
currencies_info.map((currency) => {
if (e.target.id == currency.id) {
set_currency_name(currency.name);
set_currency_icon(currency.icon);
set_currency_price(currency.website_price);
}
});
};
// this function filter items base on user input value
const filter_currency = (e) => {
set_search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
api_data = set_search_filter;
console.log(api_data);
};
return (
<div className="buy-page-input" onClick={toggle_dropdown}>
{/* currency logo */}
<div className="currency-logo">
<img src={currency_icon} width="30px" />
</div>
{/* currency name in persian */}
<span className="currency-name">{currency_name}</span>
{/* currency dropdown icon */}
<div className="currency-dropdown">
<img className={dropdown ? "toggle-drop-down-icon" : ""}
src="https://img.icons8.com/ios-glyphs/30/000000/chevron-up.png"
/>
</div>
</div>
{/* Drop down list */}
{dropdown ? (
<div className="drop-down-list-container">
{/* Search box */}
<div className="search-box-container">
<input type="search" name="search-bar" id="search-bar"
placeholder="جستجو بر اساس اسم..."
onChange={(e) => {
filter_currency(e);
}}/>
</div>
{api_data.map((currency) => {
return (<div className="drop-down-list" onClick={(e) => {
fetch_currency(e);}} id={currency.id}>
<div class="right-side" id={currency.id}>
<img src={currency.icon} width="20px" id={currency.id} />
<span id={currency.id}>{currency.name}</span>
</div>
<div className="left-side" id={currency.id}>
<span id={currency.id}>قیمت خرید</span>
<span className="buy-price" id={currency.id}>
{currency.website_price}تومان</span>
</div>
</div>);})}
</div>) : ("")});}
Your search_filter looks redundant to me.
Try to change the filter_currency function like this:
const filter_currency = (e) => {
const search = e.target.value;
const filtered = currencies_info.filter((currency) => {
return currency.name.includes(search);
});
set_api_data(filtered);
};
It looks like you are never setting the api_data after you set the filter state.
Change the following
api_data = set_search_filter
console.log(api_data)
to
api_data = set_search_filter
set_api_data(api_data)
However, it then looks like set_search_filter is never used and only set so to improve this further you could remove that state and just have it set the api_data direct. Something like this:
const filter_currency = (e) => {
const search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1
})
set_api_data(search_filter)
}
Change your state value from string to array of the search_filter like this -
let [search_filter, set_search_filter] = useState([]);
and also it should be like this -
const filter_currency = (e) => {
const filterValues = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
set_search_filter(filtervalues);
set_api_data(filterValues);
console.log(filterValues);
};
and use useEffect with search_filter as dependency, so that every time search_filter value is being set, useEffect will trigger re render, for eg:-
useEffect(()=>{
//every time search_filter value will change it will update the dom.
},[search_filter])
I am trying to use react-select in combination with match-sorter as described in this stackoverflow answer (their working version). I have an initial array of objects that get mapped to an array of objects with the value and label properties required by react-select, which is stored in state. That array is passed directly to react-select, and when you first click the search box everything looks good, all the options are there. The onInputChange prop is given a call to matchSorter, which in turn is given the array, the new input value, and the key the objects should be sorted on. In my project, and reproduced in the sandbox, as soon as you type anything into the input field, all the options disappear and are replaced by the no options message. If you click out of the box and back into it, the sorted options show up the way they should. See my sandbox for the issue, and here's the sandbox code:
import "./styles.css";
import { matchSorter } from "match-sorter";
import { useState } from "react";
import Select from "react-select";
const objs = [
{ name: "hello", id: 1 },
{ name: "world", id: 2 },
{ name: "stack", id: 3 },
{ name: "other", id: 4 },
{ name: "name", id: 5 }
];
const myMapper = (obj) => {
return {
value: obj.id,
label: <div>{obj.name}</div>,
name: obj.name
};
};
export default function App() {
const [options, setOptions] = useState(objs.map((obj) => myMapper(obj)));
return (
<Select
options={options}
onInputChange={(val) => {
setOptions(matchSorter(options, val, { keys: ["name", "value"] }));
}}
/>
);
}
I am sure that the array in state is not getting removed or anything, I've console logged each step of the way and the array is definitely getting properly sorted by match-sorter. It's just that as soon as you type anything, react-select stops rendering any options until you click out and back in again. Does it have something to do with using JSX as the label value? I'm doing that in my project in order to display an image along with the options.
I had to do two things to make your code work:
Replaced label: <div>{obj.name}</div> with label: obj.name in your mapper function.
I am not sure if react-select allows html nodes as labels. Their documentation just defines it as type OptionType = { [string]: any } which is way too generic for anything.
The list supplied to matchSorter for matching must be the full list (with all options). You were supplying the filtered list of previous match (from component's state).
const objs = [
{ name: "hello", id: 1 },
{ name: "world", id: 2 },
{ name: "stack", id: 3 },
{ name: "other", id: 4 },
{ name: "name", id: 5 }
];
const myMapper = (obj) => {
return {
value: obj.id,
label: obj.name, // -------------------- (1)
name: obj.name
};
};
const allOptions = objs.map((obj) => myMapper(obj));
export default function App() {
const [options, setOptions] = useState(allOptions);
return (
<Select
options={options}
onInputChange={(val) => {
setOptions(
matchSorter(
allOptions, // ----------------> (2)
val,
{ keys: ["name", "value"]
}
));
}}
/>
);
}
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.
Redux store cannot use immediately.
Backgound:
I have created a parent component and a child component.
The parent component will fetch a list of data, save to redux store,and generate child component.
example:
result:[
{
key: 1,
roomNo: '01',
status: 'OCCUPIED',
},
{
key: 2,
roomNo: '02',
status: 'UNOCCUPIED',
},
]
Then, there will be 2 child component (which is a button). And if I click the child button. The application will call DB again for more detail and save to redux store also.
detailResult: [
{
key: 1,
roomNo: '01',
status: 'OCCUPIED',
roomColor: 'red',
},
]
And since I would like to check whether the status is the same (result.roomNo="01"(OCCUPIED) and detailResult.roomNo="01"(OCCUPIED) ) (to ensure the data is the most updated one when user click the button)
Therefore, I write the following function in child component:
const { reduxStore } = useSelector((state) => ({
reduxStore : state.reduxStore ,
}));
const retrieve = useCallback(
(params) => {
dispatch(Actions.wardFetchRoom(params));
},
[dispatch]
);
const handleClick = (event) => {
const params = { roomNo: roomNo};
retrieve(params);
console.log('Step1', props.bedNo);
console.log('Step2', props.status);
console.log('Step3', reduxStore.room.status);
//function to match props.status === reduxStore.room.status
};
The problem comes when I click the the child button. The data always compare with previous store data but not current store data
T0: render finish
T1: click <child room="1">
T2: console: "Step1 = 1, Step2 = OCCUPIED, Step3 = NULL" (I expect "Step3 = OCCUPIED",since retrieve(params) is completed and the store should be updated at this moment)
T3: click <child room="2">
T4: console: "Step1 = 2, Step2 = UNOCCUPIED, Step3 = OCCUPIED" (This "Step3" data is come from T2, the data is delay for 1 user action)
Therefore, how can I change my code to use the store data immediately?
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.