I want to apply React hook for infinite scroll. So, I end up with this code:
export default function index() {
const [allTracks, setAllTracks] = useState([]);
const [offset, setOffset] = useState("");
const { tracks, error, loading, lastVisible } = useFetchPublicTracks(
myApiEndPoint.TRENDING_TRACKS,
5,
offset
);
//concat tracks when got data successfully
useEffect(() => {
if (!loading && tracks) setAllTracks([...allTracks, ...tracks]);
}, [loading, tracks]);
console.log("tracks", allTracks);
console.log("lastVisible", lastVisible);
console.log("loading", loading);
return (
<div>
<button className="" onClick={() => setOffset(lastVisible)}>
Load More
</button>
<Main></Main>
</div>
);
}
When I click "Load More" button, new offset will be setted. After that, component re-render again and call Api with new offset.
I want ask that I am using React hook correctly ? There is any way better ? Also, Do I need to use useCallback/useMemo in my use case ?
Related
I'm new to React and I'm trying to render a list of Pokemons.
I'm fetching the pokemon names from a local file and then using those names to trigger HTTP calls to my backend server, to get the pokemon images. Here's my code so far:
function PokemonList(props) {
const [pokemonList, setPokemonList] = useState([]);
const [isFetched, setIsFetched] = useState(false);
const [renderedList, setRenderedList] = useState([]);
useEffect(() => {
fetch(raw)
.then((r) => r.text())
.then((text) => {
setPokemonList(text.split("\n"));
setIsFetched(true);
});
}, []);
// I believe this is to blame, but I don't know why. On refresh, pokemonList becomes empty
useEffect(() => {
setRenderedList(populateRenderedList(pokemonList));
}, []);
return !isFetched ? null : (
<div className="list">
{renderedList}
<PaginationBar listSize={renderedList.length} list={renderedList} />
</div>
);
}
function populateRenderedList(pokemonList) {
let pokemonAPI = "https://pokeapi.co/api/v2/pokemon-form/";
const temp = [];
console.log(pokemonList);
pokemonList.forEach((pokemonName) => {
let renderedPokemon = (
<a className="pokemonLink" href={pokemonAPI + pokemonName.toLowerCase()}>
<PokemonDiv name={pokemonName.toLowerCase()} />
<h3>{pokemonName}</h3>
</a>
);
temp.push(renderedPokemon);
});
return temp;
}
As I have commented on the code, the 'pokemonList' renders fine when I make any changes to the PokemonList function. But the moment I refresh my page, 'pokemonList' becomes empty. Why is that?
I previously was not using 'useState' to populate my 'renderedList' list. So I believe the problem is happening because I'm using 'useState' , but I don't know why that's happening.
I had tried making 'renderedList' not a state, but I had to, for I am thinking about passing it as props to another child component, in order to change it's state.
Using ReactJS, Firestore - Firebase v9.
I have an autocomplete search bar on my web, built with just pure React. The question that I am trying to solve for at least one month is, if I can make this autocomplete input work with Firestore - (user type E.g 'elepha', auto suggestion appears with offer with word elephant - this is what I coded, and with same case, user will click on the suggestion of elephant, and this word elephant will be send to Firestore.)
Cuz there is not any solution on internet, I wonder, if my question is even possible to make, or not.
My simple autocomplete bar code - (animals_data stands for file with animals names)
and I tried to add onClick={pleaseSend} which is basic addDoc function, but when I click on the suggestion, in Firestore will only appear blank input "".
<SearchBar data={animals_data} />
And the filtering code:
function SearchBar({ placeholder, data }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const [newAnswer, setAnswer] = useState("")
const [users, setUsers] = useState([]);
const usersCollectionRef = collection(db, "Answers")
const createUser = async () => {
await addDoc(usersCollectionRef, {name: newAnswer}).then(()=>{
window.location.reload()
}).catch((err)=>{
console.log(err)
})
};
const handleFilter = (event) => {
const searchWord = event.target.value;
setWordEntered(searchWord);
const newFilter = data.filter((value) => {
return value.full_name.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
setFilteredData([]);
} else {
setFilteredData(newFilter);
}
};
const clearInput = () => {
setFilteredData([]);
setWordEntered("");
};
return (
<div className="search">
<div className="searchInputs">
<input
type="text"
placeholder={placeholder}
value={wordEntered}
onChange={handleFilter}
/>
</div>
{filteredData.length !== 0 && (
<div className="dataResult">
{filteredData.slice(0, 15).map((value, key) => {
return (
<a className="dataItem" onClick={createUser} target="_blank">
<p>{value.full_name} </p>
</a>
);
})}
</div>
)}
</div>
);
}
export default SearchBar;
EDIT 1: added code screenshots
web:
Thank you very much for reading.
after analyzing your code, I notice that you don't update the value newAnswer using its setter. Therefore, you should use the setter to update the state on user click and then add the firestorm document. You can do that by either using a button/unmodifiable input field in instead of an anchor tag to store the value of each option, then use this value inside the click handler to update the state and then use a useEffect to update firestore every time the state changes. Let me know if you need help with some code. Please post it below your original question as an edit without changing it.
I have a time consuming loop for a table that is outputting around 300 cells with some custom computation on each, it takes 4-5 seconds to load which makes sense. So I want to display a loading modal whilst its doing this. However the modal only displays after the expensive loop finishes?
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
if(loading){
console.log(loading, "Loading has started")
}
}, [loading])
return(
<>
<Modal show={loading}>Loading....</Modal>
<a onClick={() => setLoading(true)}>Load more</a>
// Expensive loop
array.map(() => {
// HMTL
<div>
----
</div>
})
</>
)
I can see from console log that state for "loading" has been updated but the modal has yet to display, so Im assuming its waiting for the loop to finish (which I dont want). Is there way to get it to output the html for the modal first or am I missing a fundamental of React?
If it's a slow computation you probably don't want to be doing it on each render, since components can render quite a few times.
Ideally you only want to render do those expensive calculations when the input data changes, which can be achieved by splitting the processing and rendering.
I'm not sure I completely understand how the loading part of this is working, but something like this example should help:
const [loading, setLoading] = useState<boolean>(false);
const [computedArray, setComputedArray] = useState([]);
useEffect(() => {
setComputedArray(array.map(//expensive loop here))
setLoading(false)
}, [array])
useEffect(() => {
if(loading){
console.log(loading, "Loading has started")
}
}, [loading])
return(
<>
<Modal show={loading}>Loading....</Modal>
<a onClick={() => setLoading(true)}>Load more</a>
// Cheap loop, turn data into markup
computedArray.map(() => {
// HTML
<div>
----
</div>
})
</>
)
I don't know what Modal is from so answering from a general perspective:
Personally, I'd keep the array in state and update it in a separate function, and use the && symbol to display the Loading element. This way you're keeping all of your logic regarding the expensive calculation and loading/unloading in one place.
export default function App() {
const [loading, setLoading] = useState(false);
const [array, setArray] = useState([]);
const getExpensiveCalculation = async () => {
setLoading(true);
const resultOfExpensiveCalculation = await ... // something here
// assuming result is a spreadable array too
setArray([...array, ...resultOfExpensiveCalculation]);
setLoading(false);
};
return (
<>
{loading && <Modal>Loading....</Modal>}
<a onClick={getExpensiveCalculation}>Load more</a>
{array.map(element => (
<div>
---
</div>
))}
</>
);
}
I have custom hook useFetch that takes URL as input and returns 3 data and I have Component CompanyListing to display those data. I want to send two different values as a URL in that hook using if else statement. If no search is done by user then display data from if endpoint, and if user search by keyword, then display from else endpoint.
This is code that I wrote CompanyListing.js
const [counter, setCounter] = useState(1);
let endpoint = `${CONSTANTS.BASE_URL}/companies?page=${counter}`;
const searchCompanies = (searchTerm) => {
if (searchTerm === '') {
endpoint = `${CONSTANTS.BASE_URL}/companies?page=${counter}`;
//console.log('DEFAULT ENDPOINT', endpoint);
} else {
endpoint = `${CONSTANTS.BASE_URL}/companies?search=${searchTerm}`;
//console.log('SEARCH ENDPOINT', endpoint);
}
};
//console.log('USEFETCH ENDPOINT', endpoint);
const [companies, isLoading, meta] = useFetch(endpoint);
return (
<>
<div data-testid="search-box">
<SearchCompany callback={searchCompanies} />
</div>
{!isLoading ? (
<div className="container">
<div className="row" data-testid="company-list">
{companies.length !== 0 && companies
? companies.map((company) => <CompanyLists key={company.id} {...company} data-testid="company-list" />)
: 'No companies'}
</div>
<Pagination meta={meta} counter={counter} setCounter={setCounter} data-testid="paginate" />
</div>
) : (
<Spinner />
)}
</>
);
};
export default CompanyListing;
When I tried calling useFetch inside if else, it goes against the rules of hooks and when I tried doing like in code above, I can't pass search endpoint to useFetch. Any idea how can I fix it? I tried by making all fetch on same component and it works perfectly but already having custom hook, I don't want to repeat code again.
And any idea why I am getting 5 console for same thing ?
Help will be much appreciated.
Issue I think is that, your parent component is not being re-rendered when searchCompanies is called so as result useFetch won't get called, and also you can't use that inside the if else also,
So, I think you can do something like this, maintain the endpoint in state and change it when ever searchCompanies is being called, in that way your component will be re-rendered and you will get the latest endpoint.
const [counter, setCounter] = useState(1);
const [endpoint,setEndPoint] = useState(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
const searchCompanies = (searchTerm) => {
if (searchTerm === '') {
setEndPoint(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
} else {
setEndPoint(`${CONSTANTS.BASE_URL}/companies?search=${searchTerm}`);
}
};
//console.log('USEFETCH ENDPOINT', endpoint);
const [companies, isLoading, meta] = useFetch(endpoint);
I have problem with this code
If I pass the whole pagination object to the second parameters of useEffect() function, then fetchData() will call continuously. If I only pass pagination.current_page so It will call only one time, but when I set new pagination as you see in navigatePage() function, the useEffect() does not call to fetchData() although pagination has changed.
How to solve this. Thank you very much!
Besides I do not want the use useEffect() call when first time component mounted because the items is received from props (It is fetch by server, this is nextjs project).
import React, {useEffect, useState} from 'react';
import Filter from "../Filter/Filter";
import AdsListingItem from "../AdsListingItem/AdsListingItem";
import {Pagination} from "antd-mobile";
import styles from './AdsListing.module.css';
import axios from 'axios';
const locale = {
prevText: 'Trang trước',
nextText: 'Trang sau'
};
const AdsListing = ({items, meta}) => {
const [data, setData] = useState(items);
const [pagination, setPagination] = useState(meta);
const {last_page, current_page} = pagination;
const fetchData = async (params = {}) => {
axios.get('/ads', {...params})
.then(({data}) => {
setData(data.data);
setPagination(data.meta);
})
.catch(error => console.log(error))
};
useEffect( () => {
fetchData({page: pagination.current_page});
}, [pagination.current_page]);
const navigatePage = (pager) => {
const newPagination = pagination;
newPagination.current_page = pager;
setPagination(newPagination);
};
return (
<>
<Filter/>
<div className="row no-gutters">
<div className="col-md-8">
<div>
{data.map(item => (
<AdsListingItem key={item.id} item={item}/>
))}
</div>
<div className={styles.pagination__container}>
<Pagination onChange={navigatePage} total={last_page} current={current_page} locale={locale}/>
</div>
</div>
<div className="col-md-4" style={{padding: '15px'}}>
<img style={{width: '100%'}} src="https://tpc.googlesyndication.com/simgad/10559698493288182074"
alt="ads"/>
</div>
</div>
</>
)
};
export default AdsListing;
The issue is you aren't returning a new object reference. You save a reference to the last state object, mutate a property on it, and save it again.
const navigatePage = (pager) => {
const newPagination = pagination; // copy ref pointing to pagination
newPagination.current_page = pager; // mutate property on ref
setPagination(newPagination); // save ref still pointing to pagination
};
In this case the location in memory that is pagination remains static. You should instead copy all the pagination properties into a new object.
const navigatePage = (pager) => {
const newPagination = {...pagination}; // shallow copy into new object
newPagination.current_page = pager;
setPagination(newPagination); // save new object
};
To take it a step further you really should be doing functional updates in order to correctly queue up updates. This is in the case that setPagination is called multiple times during a single render cycle.
const navigatePage = (pager) => {
setPagination(prevPagination => {
const newPagination = {...prevPagination};
newPagination.current_page = pager;
});
};
In the case of pagination queueing updates may not be an issue (last current page set wins the next render battle), but if any state updates actually depend on a previous value then definitely use the functional update pattern,