Why doesn't useSelector doesn't re-execute JSX - javascript

I am making an app which should load content from api and immediately display components based on that data. Firstly, when it fetches the data, it should dispatch it to redux store and then with useSelector should trigger the change and pass the value as a prop to children component. Problem is that it does dispatch and select it with selector (there are console logs in code to confirm) but in doesn't pass it to child via props. I know that I can use useSelector in child but I am now intrested why doesn't it work this way.
Component 1
const Menu = () => {
console.log("reload");
const food = useSelector((state) => state.foodList.food);
console.log(food);
const [foodList, setFoodList] = useState(food);
// console.log(foodList);
const dispatch = useDispatch();
const page = useSelector((state) => state.ui.page);
useEffect(() => {
const getFoodList = async () => {
const response = await fetch(
"https://senbonzakura-food-default-rtdb.firebaseio.com/food.json"
);
if (!response.ok) throw new Error("Something went wrong!");
const data = await response.json();
const foodList = data[Object.keys(data)[0]];
console.log("---");
console.log(foodList);
console.log("---");
dispatch(foodSliceActions.updateFoodList(foodList));
};
try {
getFoodList();
// dispatch(foodSliceActions.updateFoodList(foodList));
} catch (err) {
console.log(err);
}
}, []);
// const navigate = useNavigate();
// const queryPrams = new URLSearchParams(location.search);
// const sort = queryPrams.get("sort");
return (
<main className={classes["menu-main"]}>
<section
className={`${classes["menu__section"]} ${classes["menu__left-side"]}`}
>
<div className={classes["menu__label"]}>
<h1>MENU</h1>
<h1>{page + 1}</h1>
</div>
<MenuList foodList={foodList ? foodList : []} page={page} />
</section>
<section
className={`${classes["menu__section"]} ${classes["menu__right-side"]}`}
>
<Outlet />
</section>
</main>
);
};
export default Menu;
Child component
const formatArray = (array) => {
const pages = Math.ceil(array.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
helpArr.push(array[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
return arr;
};
const MenuList = (props) => {
const page = props.page;
const dispatch = useDispatch();
const DUMMY_FOOD = props.foodList;
console.log(DUMMY_FOOD);
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const [foodList, setFoodList] = useState([]);
useEffect(() => {
setFoodList(formatArray(DUMMY_FOOD));
}, []);
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
const onNextPageHandler = () => {
dispatch(uiSliceActions.updatePage("forward"));
};
const onPreviousPageHandler = () => {
dispatch(uiSliceActions.updatePage("backward"));
};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => a.foodPrice - b.foodPrice))
: (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => b.foodPrice - a.foodPrice));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{foodList[page]
? foodList[page].map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))
: ""}
</div>
<div className={classes["menu-list__buttons"]}>
{page >= 1 && (
<Button type="button" onClick={onPreviousPageHandler}>
Page {page}
</Button>
)}
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending` : `Ascending`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page {page + 2}
</Button>
</div>
</Fragment>
);
};
export default MenuList;
food-slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = { food: [] };
const foodSlice = createSlice({
name: "foodList",
initialState,
reducers: {
updateFoodList(state, action) {
// console.log(action.payload);
state.food = action.payload;
},
},
});
const foodSliceActions = foodSlice.actions;
export const foodSliceReducer = foodSlice.reducer;
export default foodSliceActions;
index (store)
import { configureStore } from "#reduxjs/toolkit";
import { uiSliceReducer } from "./ui-slice";
import { cartSliceReducers } from "./cart-slice";
import { foodSliceReducer } from "./food-slice";
const store = configureStore({
reducer: {
ui: uiSliceReducer,
cart: cartSliceReducers,
foodList: foodSliceReducer,
},
});
export default store;
Thank you.

You wrote the following code in component1
const food = useSelector((state) => state.foodList.food);
const [foodList, setFoodList] = useState(food);
Here, you set food as initial state value of foodList state, so foodList is updated as food only when the component1 loads initially (componentDidMount lifecycle), instead of updating whenever food is updated.
If you wants to update foodList whenever food is updated, you need to set it in useEffect hook.
useEffect(() => { setFoodList(food) }, [food]);
Btw, you don't need to pass food as props to the child component.
You can use food directly in child component.
I updated your code.
component1
const Menu = () => {
const dispatch = useDispatch();
const page = useSelector((state) => state.ui.page);
useEffect(() => {
const getFoodList = async () => {
const response = await fetch(
"https://senbonzakura-food-default-rtdb.firebaseio.com/food.json"
);
if (!response.ok) throw new Error("Something went wrong!");
const data = await response.json();
const foodList = data[Object.keys(data)[0]];
dispatch(foodSliceActions.updateFoodList(foodList));
};
try {
getFoodList();
} catch (err) {
console.log(err);
}
}, []);
return (
<main className={classes["menu-main"]}>
<section
className={`${classes["menu__section"]} ${classes["menu__left-side"]}`}
>
<div className={classes["menu__label"]}>
<h1>MENU</h1>
<h1>{page + 1}</h1>
</div>
<MenuList page={page} />
</section>
<section
className={`${classes["menu__section"]} ${classes["menu__right-side"]}`}
>
<Outlet />
</section>
</main>
);
};
export default Menu;
child component
const formatArray = (array) => {
const pages = Math.ceil(array.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
helpArr.push(array[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
return arr;
};
const MenuList = (props) => {
const page = props.page;
const dispatch = useDispatch();
const DUMMY_FOOD = useSelector((state) => state.foodList.food);
const [foodList, setFoodList] = useState([]);
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
useEffect(() => {
setFoodList(DUMMY_FOOD);
}, [DUMMY_FOOD]);
const onNextPageHandler = () => {
dispatch(uiSliceActions.updatePage("forward"));
};
const onPreviousPageHandler = () => {
dispatch(uiSliceActions.updatePage("backward"));
};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (setFoodList(foodList.sort((a, b) => a.foodPrice - b.foodPrice)))
: (setFoodList(foodList.sort((a, b) => b.foodPrice - a.foodPrice)));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{foodList[page]
? foodList[page].map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))
: ""}
</div>
<div className={classes["menu-list__buttons"]}>
{page >= 1 && (
<Button type="button" onClick={onPreviousPageHandler}>
Page {page}
</Button>
)}
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending` : `Ascending`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page {page + 2}
</Button>
</div>
</Fragment>
);
};
export default MenuList;

Related

localStorage is working wrong in my application

I have a problem with the localStorage in my application. When I add items to a list of "favorites" they are stored without any problem in the localStorage, they can even be deleted by clicking them again.
But when I refresh the page, my application doesn't read that these items are in the favorites list and therefore doesn't mark them. Also, when I add a new item to the favorites list it causes it to delete everything from localStorage and start over.
Here's a gif of the localStorage view
Here's the code:
import React, { useState, useEffect } from 'react';
import SearchBar from '../../SearchBar/SearchBar.js';
import FiltersBox from '../FiltersBox/FiltersBox.js';
import { getItems } from '../../../Database/Database.js';
import './ItemsContainer.css';
function ItemsContainer() {
const [items, setItems] = useState([]);
const [search, setSearch] = useState('');
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
const [sortPrice, setSortPrice] = useState('');
const [filterCategory, setFilterCategory] = useState('');
const addItemToFavorites = item => {
let existentItem = favoriteItems.find(favItem => favItem.id === item.id);
if (existentItem) {
let filterTheExistentItem = favoriteItems.filter(
favItem => item.title !== favItem.title
);
setFavoriteItems(filterTheExistentItem);
let stringItems = JSON.stringify(filterTheExistentItem);
localStorage.setItem('Favorite Items', stringItems);
} else {
setFavoriteItems([...favoriteItems, item]);
let stringItems = JSON.stringify([...favoriteItems, item]);
localStorage.setItem('Favorite Items', stringItems);
}
};
const filteredItemsList = () => {
let newItemList = [];
newItemList = items.filter(item => {
if (filterCategory !== '' && filterCategory !== 'none') {
return item.category === filterCategory;
} else {
return item;
}
});
if (sortPrice === 'ascending') {
return newItemList.sort((a, b) => (a.price > b.price ? 1 : -1));
} else if (sortPrice === 'descending') {
return newItemList.sort((a, b) => (b.price > a.price ? 1 : -1));
} else {
return newItemList;
}
};
function onSortSelected(sortValue) {
setSortPrice(sortValue);
}
function onCategorySelected(categoryValue) {
setFilterCategory(categoryValue);
}
useEffect(() => {
getItems().then(res => setItems(res));
}, []);
useEffect(() => {
let xd = JSON.parse(localItems);
console.log(xd);
}, [localItems]);
return (
<div>
<SearchBar setSearch={setSearch} />
<FiltersBox
items={items}
setItems={setItems}
onSortSelected={onSortSelected}
onCategorySelected={onCategorySelected}
/>
<div>
{filteredItemsList()
.filter(item =>
search.toLowerCase() === ''
? item
: item.title.toLowerCase().includes(search)
)
.map(item => (
<div key={item.id}>
<div>{item.title}</div>
<button
className={favoriteItems.includes(item) ? 'si' : 'no'}
onClick={() => addItemToFavorites(item)}>
Add to favorites
</button>
</div>
))}
</div>
</div>
);
}
export default ItemsContainer;
And here I leave a GIF with a continuous console.log of the localStorage:
I tried everyrhing, and I don't know what is happening.
You're retrieving your items in localItems and... you do nothing with this variable. You should initialize your state favoritesItems with your local storage
const getItemsFromLocalStorage = () => {
const items = localStorage.getItem('Favorite Items');
return items ? JSON.parse(items) : [];
}
const [favoriteItems, setFavoriteItems] = useState(getItemsFromLocalStorage())
This is where the culprit is:
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
You load localStorage into localItems, but you expect it to be in favoriteItems, where you have never assigned it. You would need to specify the item of localStorage as the initial state, like:
let localItems = localStorage.getItem('Favorite Items');
const [favoriteItems, setFavoriteItems] = useState(localItems ? localItems : []);

React-select-pagination not fetching data on scroll

I am using react-select with more than 20,000 options fetched from the database via Node API.
Page was not even loading .
Now, I added "react-select-async-pagination".
But the data is fetched once only.
import React, { useRef, useState, useEffect } from "react";
import Select from "react-select";
import LoadOptions from "./LoadOptions";
import { AsyncPaginate } from "react-select-async-paginate";
const TooManySelect = () => {
const [value, onChange] = useState(null);
return (
<div className="Select-options">
<label>Pdt code</label>
<AsyncPaginate
defaultOptions
value={value}
loadOptions={LoadOptions}
onChange={onChange}
/>
</div>
);
};
export default TooManySelect;
LoadOptions : Here is the api call. I am passing the count of the last row fetched every time via "prevLast" so that I can use the OFFSET prevLast ROWS inside database query.
import { useState } from "react";
const sleep = (ms) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
const baseurl = "http://localhost:5000/api";
const LoadOptions = async (search, prevOptions) => {
const [prevLast, setPrevLast] = useState(0);
const [pdtOpt, setPdtOpt] = useState([]);
await sleep(1000);
const response = await fetch(`${baseurl}/pdt/${prevLast}`);
const pList = await response.json();
const pdtList = [];
for (let i = 0; i < pList.length; i++) {
pdtList.push({ label: pList[i].pdtno, value: pList[i].pdtno });
}
setPdtOpt(pdtList);
setPrevLast(pList.length);
return {
options: pdtList,
hasMore: true
};
};
export default LoadOptions;
Here is my codesandbox link.
https://codesandbox.io/s/react-select-paginate-test-tob90j
My question is : How can we access thousands of (select) options from DB without page freeze?
I got this link while googleing.
https://blog.saeloun.com/2022/03/03/infinite-scroll-with-pagination.html#using-lazy-loading-and-pagination
It helped me came to a perfect solution.
So my current code goes like this
(Made only a few changes from the code given in the above link).
Select component:
import React, { useState } from "react";
import SelectWrapper from "./SelectWrapper";
const baseurl = "http://localhost:5000/api";
function MainSelect() {
const [options, setOptions] = useState([]);
const [selectedOption, setSelectedOption] = useState("");
const [pageNo, setPageNo] = useState(0);
const [hasNextPage, setHasNextPage] = useState(true);
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
const loadOptions = async (page) => {
try {
// console.log(`Page ${page}`);
const size = 50;
setIsNextPageLoading(true);
const data = await fetch(`${baseurl}/pdt/${page}/${size}`);
const pList = await data.json();
const pdtList = [];
for (let i = 0; i < pList.length; i++) {
pdtList.push({ label: pList[i].pdtno, value: pList[i].pdtno });
}
setOptions(pdtList);
setIsNextPageLoading(false);
setHasNextPage(pdtList.length < 500);
setPageNo(page);
} catch (err) {
console.log(err);
}
};
console.log(options);
const loadNextPage = async () => {
await loadOptions(pageNo + 1);
};
return (
<div className="dropdown">
<div className="dropdown">
<div className="label">
<label>Pdt</label>
</div>
<SelectWrapper
value={selectedOption}
placeholder="Select"
isClearable
hasNextPage={hasNextPage}
isNextPageLoading={isNextPageLoading}
options={options}
loadNextPage={loadNextPage}
onChange={(selected) => setSelectedOption(selected)}
/>
</div>
</div>
);
}
export default MainSelect;
SelectWrapper that carries out the Virtualization part:
import React, { useEffect, useState } from "react";
import { FixedSizeList as List } from "react-window";
import InfiniteLoader from "react-window-infinite-loader";
import Select from "react-select";
import AutoSizer from "react-virtualized-auto-sizer";
const SelectWrapper = (props) => {
const {
hasNextPage,
isNextPageLoading,
options,
loadNextPage,
placeholder,
onChange,
value,
} = props;
const [selectedOption, setSelectedOption] = useState(value);
useEffect(() => {
setSelectedOption(value);
}, [value]);
const itemCount = hasNextPage ? options.length + 1 : options.length;
const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage;
const isItemLoaded = (index) => !hasNextPage || index < options.length;
const MenuList = ({ children }) => {
const childrenArray = React.Children.toArray(children);
const Item = ({ index, style, ...rest }) => {
const child = childrenArray[index];
return (
<div
className="drop-down-list"
style={{
borderBottom: "1px solid rgb(243 234 234 / 72%)",
display: "flex",
alignItems: "center",
...style,
}}
onClick={() => handleChange(options[index])}
{...rest}
>
{isItemLoaded(index) && child ? child : `Loading...`}
</div>
);
};
return (
<AutoSizer disableHeight>
{({ width }) => (
<InfiniteLoader
isItemLoaded={(index) => index < options.length}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
className="List"
height={150}
itemCount={itemCount}
itemSize={35}
onItemsRendered={onItemsRendered}
ref={ref}
width={width}
overscanCount={4}
>
{Item}
</List>
)}
</InfiniteLoader>
)}
</AutoSizer>
);
};
const handleChange = (selected) => {
console.log("test");
onChange(selected);
};
return (
<Select
placeholder={placeholder}
components={{ MenuList }}
value={selectedOption}
options={options}
{...props}
/>
);
};
export default SelectWrapper;
For anyone who needs the same, I have updated the codepen as well.
https://codesandbox.io/s/react-select-paginate-test-tob90j

Why isn't the data rendered in sorted order

Why isn't the rendered data in sorted order
I am passing rides as a prop in the NearestRides component and inside the NearestRides component, first i am sorting the rides and setting to sortedRides and then i am mapping sortedRides.
but why is the sortedRides not sorted?
is sortedRides getting rendered before getting sorted? if so, how do i sort rides before rendering?
Rides.js
import { useEffect, useState } from "react";
import Navbar from "./Navbar";
import NearestRides from "./NearestRides";
const Rides = () => {
const [rides, setRides] = useState([]);
const [user, setUser] = useState({});
useEffect(() => {
const fetchRides = async () => {
const data = await fetch('https://assessment.api.vweb.app/rides');
const json = await data.json();
setRides(json);
}
const fetchUser = async () => {
const data = await fetch('https://assessment.api.vweb.app/user');
const json = await data.json();
console.log(json);
setUser(json);
}
const makeNetworkCalls = async() => {
await fetchRides();
await fetchUser();
}
makeNetworkCalls().catch((e) => {
console.log(e);
})
}, [])
useEffect(() => {
const calculateDistance = async(path, user_station) => {
let min = Math.abs(user_station - path[0]);
for(let i = 0; i<path.length; i++){
if(path[i] === user_station){
return 0;
}
if(Math.abs(path[i] - user_station) < min){
min = Math.abs(path[i] - user_station);
}
}
return min;
}
const updaterides = async () => {
rides.map(async (ride) => {
ride.distance = await calculateDistance(
ride.station_path,
user.station_code
);
});
};
if (rides?.length > 0) {
updaterides().catch((e) => {
console.log(e);
});
}
}, [rides,user]);
return (
<div>
<Navbar user = {user}/>
<div className="home">
<NearestRides rides = {rides}/>
</div>
</div>
);
}
export default Rides;
NearestRides.js
import { useEffect, useState } from "react";
const NearestRides = ({rides}) => {
const [sortedRides, setSortedRides] = useState([]);
useEffect(() => {
const sortRides = async() => {
const sorted = await rides.sort((ride1,ride2) => {
return ride1.distance > ride2.distance ? 1 : -1;
})
setSortedRides(sorted);
}
sortRides().catch((e) => console.log(e));
}, [rides]);
return(
<div className="rides">
{console.log(sortedRides)}
{sortedRides?.map((ride) => {
return (
<div className="ride-detail">
<img src={ride.map_url} alt="Ride_map" />
<div>
<p>Ride Id : {ride.id}</p>
<p>Origin Station : {ride.origin_station_code}</p>
<p>Station Path : {ride.station_path}</p>
<p>Date : {ride.date}</p>
<p>Distance : {ride.distance}</p>
</div>
</div>
)
})}
</div>
)
}
export default NearestRides;
It looks like that when you are calculating the distance and adding the distance property to rides array, you are not actually setting it again to the rides using setRides.
And so, the distance is never part of the rides array when received in child nearestRides and hence sorting method is not working in nestedRides.
But setting the rides in the useEffect with deps will create infinite loop.
So suggest to calculate the distance in the useEffect with no deps - ie right after you fetch the rides.

Using two useEffect but component is returning after executing first one only

Here on rendering (inside return() of the Rides component), ride.distance is undefined but when in second useEffect inside map function on printing each ride they have ride.distance some value and not defined
Is the component renders after execcuting the first useEffect and the second useEffect runs later or there is some other issue?
how do is achieve that, before rendering the second useEffect completes execution ?
import Navbar from "./Navbar";
const Rides = () => {
const [rides, setRides] = useState([]);
const [user, setUser] = useState({});
const [updatedRides, setUpdatedRides] = useState([]);
useEffect(() => {
const fetchRides = async () => {
const data = await fetch('https://assessment.api.vweb.app/rides');
const json = await data.json();
setRides(json);
console.log(json);
}
const fetchUser = async () => {
const data = await fetch('https://assessment.api.vweb.app/user');
const json = await data.json();
console.log(json);
setUser(json);
}
const makeNetworkCalls = async() => {
await fetchRides();
await fetchUser();
}
makeNetworkCalls().catch((e) => {
console.log(e);
})
}, [])
useEffect(() => {
const calculateDistance = async(path, user_station) => {
let min = Math.abs(user_station - path[0]);
for(let i = 0; i<path.length; i++){
if(path[i] === user_station){
return 0;
}
if(Math.abs(path[i] - user_station) < min){
min = Math.abs(path[i] - user_station);
}
}
return min;
}
const updaterides = async() => {
setUpdatedRides(rides);
updatedRides.map(async(ride) => {
ride.distance = await calculateDistance(ride.station_path, user.station_code);
console.log(ride);
})
}
updaterides().catch((e) => {
console.log(e);
})
}, [rides]);
return(
<div>
<Navbar user = {user}/>
<div className="rides">
{updatedRides.map((ride) => {
return (
<div className="rideDetail">
<img src = {ride.map_url} alt="Ride_map" />
<div>
<p>Ride Id : {ride.id}</p>
<p>Origin Station : {ride.origin_station_code}</p>
<p>Station Path : {ride.station_path}</p>
<p>Date : {ride.date}</p>
<p>Distance : {ride.distance}</p>
</div>
</div>
)
})}
</div>
</div>
)
}
export default Rides;
I fixed your code check it out here https://codesandbox.io/embed/busy-payne-qiiw5r?fontsize=14&hidenavigation=1&theme=dark . Problem was related with non async behaviour. In your second useEffect inside updaterides function, you are setting updatedRides then you are mapping it but it does not work as you expected since state changes are not updating immediately before map.
import { useEffect, useState } from "react";
const Rides = () => {
const [rides, setRides] = useState([]);
const [user, setUser] = useState({});
const [updatedRides, setUpdatedRides] = useState([]);
useEffect(() => {
const fetchRides = async () => {
const data = await fetch("https://assessment.api.vweb.app/rides");
const json = await data.json();
setUpdatedRides(json);
setRides(json);
};
const fetchUser = async () => {
const data = await fetch("https://assessment.api.vweb.app/user");
const json = await data.json();
setUser(json);
};
const makeNetworkCalls = async () => {
await fetchRides();
await fetchUser();
};
makeNetworkCalls().catch((e) => {
console.log(e);
});
}, []);
useEffect(() => {
const calculateDistance = async (path, user_station) => {
let min = Math.abs(user_station - path[0]);
for (let i = 0; i < path.length; i++) {
if (path[i] === user_station) {
return 0;
}
if (Math.abs(path[i] - user_station) < min) {
min = Math.abs(path[i] - user_station);
}
}
return min;
};
const updaterides = async () => {
setUpdatedRides(rides);
updatedRides.map(async (ride) => {
ride.distance = await calculateDistance(
ride.station_path,
user.station_code
);
console.log(ride);
});
};
if (updatedRides?.length > 0 && rides?.length > 0) {
updaterides().catch((e) => {
console.log(e);
});
}
}, [rides, updatedRides]);
return (
<div>
<div className="rides">
{rides?.map((ride) => (
<div className="rideDetail">
<img src={ride.map_url} alt="Ride_map" />
<div>
<p>Ride Id : {ride.id}</p>
<p>Origin Station : {ride.origin_station_code}</p>
<p>Station Path : {ride.station_path}</p>
<p>Date : {ride.date}</p>
<p>Distance : {ride.distance}</p>
</div>
</div>
))}
</div>
</div>
);
};
export default Rides;

Display a random string from an array on the click of a button [React]

I'm trying to build a simple app that displays a random question. I can successfully display a random string from an array on mount and page refresh.
I would like to display a different random question 'onClick' of a button rather than refresh the page?
Heres the code so far:
export const QuestionContainer = () => {
const [response, setResponse] = useState({});
useEffect(() => {
fetchData().then(res => setResponse(res));
}, []);
const { records = [] } = response;
const questions = records.map(record => record.fields.question);
console.table(questions);
// const randomNum = arr => {
// return Math.floor(Math.random() * arr.length);
// };
return (
<div className='questions-container'>
<h1>{questions[0]}?</h1>
<button onClick={() => console.log('more')}>More</button>
</div>
);
};
The const questions is an array of strings example - ['hello', 'world', 'noob question']
You're very close! Just use the onClick to set the index that's being displayed, and you're golden! This should work:
const randomIndex = (arr) => { // returns a random int value to use as an index
return Math.floor(Math.random() * arr.length)
}
export const QuestionContainer = () => {
const [response, setResponse] = useState({});
const [index, setIndex] = useState(0) // 0 initially, as you had in your example
useEffect(() => {
fetchData().then(res => setResponse(res));
}, []);
const { records = [] } = response;
const questions = records.map(record => record.fields.question);
console.table(questions);
return (
<div className='questions-container'>
<h1>{questions[index]}?</h1>
<button onClick={_ => setIndex(randomIndex(questions)}>More</button>
</div>
);
};
If you use another API Random Quote with a button:
import './App.css';
import axios from 'axios';
import { useEffect, useState } from 'react';
const App = () => {
const randomIndex = (arr) => { // returns a random int value to use as an index
return Math.floor(Math.random() * arr.length)
}
const [quote, setQuote] = useState("");
const [index, setIndex] = useState(0);
const quoteAPI = async () => {
let arrayOfQuotes = [];
try {
const data = await axios.get("https://raw.githubusercontent.com/skolakoda/programming-quotes-api/master/backup/quotes.json");
arrayOfQuotes = data.data;
console.log(arrayOfQuotes);
const quote = arrayOfQuotes.map((arrayOfQuote) =>
<div key={arrayOfQuote.id}>
<h3>{arrayOfQuote.en}</h3>
<p>{arrayOfQuote.author}</p>
</div>);
console.log(quote);
setQuote(quote);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
quoteAPI();
}, []);
return (<div className="App">
<h4>{quote[index]}</h4>
<button onClick={_ => setIndex(randomIndex(quote))}>Get Random Quote</button>
</div>
);
};
export default App;

Categories

Resources