how to scroll to previous state of infinite list? - reactjs

my way is storing yOffset then use window.scrollby() when component re-render but it doesn't work.
method for storing and get value from sessionStorage
my state
const [users, setUsers] = useState([]);
const [pageNumber, setPageNumber] = useState(1);
const [hasMore, setHasMore] = useState(true);
useEffect(()=>{
const handleGetPreviousOffset = () => {
const scrolledIndex = sessionStorage.getItem("scrolledIndex")
scrolledIndex && window.scrollBy(0,scrolledIndex)
};
handleGetPreviousOffset()
},[])
useEffect(() => {
const handleScroll = () => {
sessionStorage.setItem("scrolledIndex",window.scrollY)
};
window.addEventListener("scroll", handleScroll);
return () =>{
window.removeEventListener("scroll", handleScroll)
}
},[]);
method for get list from server
const handleFindingUser = async () => {
setPageNumber((prevState) => prevState + 1);
const findingCondition = {
userId: "1",
height: 160,
userGender: "Girl",
age: 25,
coordinates: [106.70911015078109, 10.920765741379807],
distance: 100000,
hobbies: "61a88a564f32ef06a8dfb159",
pageNumber,
};
const users = await dispatch(findingUser(findingCondition, pageNumber));
switch (true) {
case users.payload.data.users.length > 0: {
setUsers((prevState) => {
return ([...prevState, ...users.payload.data.users]);
});
setHasMore(true);
break;
}
case users.payload.data.users.length === 0 ||
users.payload.data.users.length < 20: {
setHasMore(false);
break;
}
default:
break;
}
};
useEffect(() => {
handleFindingUser();
}, []);
finally,i use infinite list library
<InfiniteScroll
dataLength={users.length} //This is important field to render the next data
next={handleFindingUser}
hasMore={hasMore}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: "center" }}>
<b>Yay! You have seen it all</b>
</p>
}
>
{users.map((user) => {
return (
<div
key={user.userId}
id={user.nickName}
className="d-flex"
style={{ margin: 12 }}
>
<p>{user.nickName}</p>
</div>
);
})}
</InfiniteScroll>
i can see handleGetPreviousOffset function was call but i don't know why window.scrollBy() still not work, i missing something?
have a nice day, everyone!

You can save the offset, but a better way is using hash in the URL (by id) and save it in localstorage or (sessionStorage), when you are done remove it.
Refer to this for more information.

Related

useState array as dependency causing infinite loop

I have been struggling with this for hours, i'm new to React and would appreciate any assistance.
I'm working on something where users can pick regions into an array.
My main problem is that i want the array that users choose to have unique values only.
I have tried using a javascript SET but that can't be mapped through. The array will be mapped through then displayed to the user.
And i have tried setting "if" statements, that check for duplicate values, inside useEffect but the dependency on a useState array creates an infinite loop.
I have read about using useRef on an array to avoid useEffect infinite loops but i find that its normally for static rather than changing arrays.
Below is the important part:
const [regions, setRegions] = useState([]);
const [region, setRegion] = useState("");
useEffect(() => {
if (region) {
if (regions.includes.region) {
return;
} else if (!regions.includes.region) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
}
// setRegions((previousState) => new Set([...previousState, region]));
}, [region, regions]);
The rest of the code for context:
import { useEffect, useState } from "react";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng,
} from "react-places-autocomplete";
export default function Test() {
const [address, setAddress] = useState("");
const [coordinate, setCoordinates] = useState({
lat: null,
lng: null,
});
const [regions, setRegions] = useState([]);
const [region, setRegion] = useState("");
useEffect(() => {
if (region) {
if (regions.includes.region) {
return;
} else if (!regions.includes.region) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
}
// setRegions((previousState) => new Set([...previousState, region]));
}, [region, regions]);
const handleSelect = async (value) => {
const result = await geocodeByAddress(value);
const full_region = result[0].formatted_address;
const part_region = full_region.substring(0, full_region.indexOf(","));
let province = "";
if (result[0].address_components[2].short_name.length <= 3) {
province = result[0].address_components[2].short_name;
} else {
province = result[0].address_components[3].short_name;
}
setAddress(value);
setCoordinates(coordinate);
setRegion(part_region.concat("-", province));
};
const onDelete = (e) => {
const value = e.target.getAttribute("value");
console.log("onDelete: ", value);
setRegions(regions.filter((item) => item !== value));
};
// setRegions(Array.from(new Set(regions)));
return (
<>
<PlacesAutocomplete
value={address}
onChange={setAddress}
onSelect={handleSelect}
searchOptions={{
componentRestrictions: { country: ["za"] },
types: ["(regions)"],
}}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: "Add regions...",
className: "location-search-input",
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map((suggestion) => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "orange", cursor: "pointer" }
: { backgroundColor: "silver", cursor: "pointer" };
return (
<div
key={suggestion.description}
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<p>Regions</p>
<ul>
{regions.map((region) => (
<li
// key={region}
title="remove"
className="cursor-pointer"
onClick={onDelete}
value={region}
>
{region}
</li>
))}
</ul>
</>
);
}
You are using .includes() incorrectly by trying to obtain region as a property: regions.includes.region
This results in:
the second condition else if (!regions.includes.region) always succeeding,
which then results in the state change setRegions() being made,
which then triggers the [regions] in the dependency,
which then loops the useEffect() again, and again.. ..infinitely.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
Instead, it should be passed as a parameter to the method: if(regions.includes(region)) and if(!regions.includes(region))
useEffect(() => {
if (region) {
if (regions.includes(region)) {
return;
}
if (!regions.includes(region)) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
}
}, [region, regions]);
You could probably also simplify it by only modifying the state if the condition doesn't succeed:
useEffect(() => {
if (region) {
if (!regions.includes(region)) {
setRegions((prevValue) => {
return [...prevValue, region];
});
}
// else do nothing
}
}, [region, regions]);

On page refresh the progress value becomes 0 and moves to the next page React Redux

I want to save the progress in MongoDB and then display the progress. The progress value is getting calculated and updated. But when I do page refresh the progress value becomes 0 and in between moves to the next page.
const Quiz = (props) => {
const {progress: lastProgress, userId} = props;
const [progress, setProgress] = useState(lastProgress || 0);
const dispatch = useDispatch();
const history = useHistory();
const classes = useStyles();
useEffect(() => {
async function fetchProgress(){
try{
const progress = await api.get(paths.FETCH_USER);
const newprogress = progress.progress;
console.log(newprogress + "alpha");
if (newprogress <= 100) {
setProgress(Number(newprogress));
}}
catch(error){
console.log(error);
}
};
fetchProgress();
}, []);
useEffect(() => {
async function updateProgress() {
try {
await api.patch(paths.UPDATE_USER, JSON.stringify({progress}))
console.log(progress + "valueof");
}
catch(error){
console.log("error", error);
}
};
updateProgress();
}, [progress]);
function handleSubmit(event) {
event.preventDefault();
const valid = questions.some((q) => !q.value);
console.log(valid + "questionsalpha");
if (!valid) {
dispatch(postAssessment({ responses: questions, id: assessment.id }, history));
}
setCurrentQuestion(0);
setChecked((previousState) => !previousState);
setTimeout(() => {
setChecked(previousState => ({
afterPreviousChange: previousState.previousChange
}))
}, 1000);
setProgress((prevProgress)=> prevProgress + 10);
}
return (
<Grid item md={4} >
<Fragment>
<Grid item xs={12} md={6} sm={6}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" orientation="vertical" value={progress} />
</Box>
</Box>
</Grid>
</Fragment>
</Grid>
)
);
I am able to calculate the value of progress and the value is stored but on page refresh the progress value changes to 0. Do not know what mistake I am making.
Any help will be appreciated.
I think all you need is to persist the progress state to longterm storage so it's accessible after a page reload. Use localStorage to persist the progress state when it updates. Lazy initialize the progress state from localStorage. This avoids any issue of accidentally "resetting" the progress back to 0 via the PATCH request.
Example:
const Quiz = (props) => {
...
const [progress, setProgress] = useState(() => {
return Number(JSON.parse(localStorage.getItem("progress")) ?? 0);
});
...
useEffect(() => {
async function fetchProgress() {
try {
const { progress } = await api.get(paths.FETCH_USER);
console.log(progress + "alpha");
if (progress <= 100) {
setProgress(Number(progress));
}
} catch(error) {
console.log(error);
}
};
fetchProgress();
}, []);
useEffect(() => {
async function updateProgress() {
try {
await api.patch(paths.UPDATE_USER, JSON.stringify({ progress }))
console.log(progress + "valueof");
} catch(error){
console.log("error", error);
}
};
updateProgress();
localStorage.setItem("progress", JSON.stringify(progress));
}, [progress]);
function handleSubmit(event) {
event.preventDefault();
...
setProgress((progress) => progress + 10);
}
...
);

Why my setInterval() is speeding up inside my React image slider?

Why my interval is speeding up?
When I press any of my buttons NextImage() or PrevImage() my interval starts speeding up and the image starts glitching. Any advice or help?
Here's my code =>
//Image is displayed
const [image, setImage] = React.useState(1);
let imageShowed;
if (image === 1) {
imageShowed = image1;
} else if (image === 2) {
imageShowed = image2;
} else if (image === 3) {
imageShowed = image3;
} else {
imageShowed = image4;
}
// Auto change slide interval
let interval = setInterval(
() => (image === 4 ? setImage(1) : setImage(image + 1)),
5000
);
setTimeout(() => {
clearInterval(interval);
}, 5000);
// Change image functionality
const ChangeImage = (index) => {
setImage(index);
};
/ /Next image
const NextImage = () => {
image === 4 ? setImage(1) : setImage(image + 1);
};
// Previous image
const PrevImage = () => {
image === 1 ? setImage(4) : setImage(image - 1);
};
When you need to have some logic which is depend on changing a variable, it's better to keep those logic inside useEffect
const interval = useRef(null);
const timeout = useRef(null);
useEffect(() => {
interval.current = setInterval(
() => (image === 4 ? setImage(1) : setImage((i) => i + 1)),
5000
);
timeout.current = setTimeout(() => {
clearInterval(interval.current);
}, 5000);
return () => {
clearInterval(interval.current);
clearTimeout(timeout.current);
}
}, [image]);
one point to remember is that if you use a variable instead of using useRef it can increase the possibility of clearing the wrong instance of interval or timeout during the rerenders. useRef can keep the instance and avoid any unwanted bugs
Your approach causes so many problems and you should learn more about react (watch youtube tutorials about react), I did make a working example slider hope to help you and people in the future:
let interval;
const images = [
"https://picsum.photos/300/200?random=1",
"https://picsum.photos/300/200?random=2",
"https://picsum.photos/300/200?random=3",
"https://picsum.photos/300/200?random=4",
"https://picsum.photos/300/200?random=5",
];
const App = () => {
const [slide, setSlide] = React.useState(0);
React.useEffect(() => {
interval = setInterval(() => {
NextSlide();
clearInterval(interval);
}, 5000);
return () => {
clearInterval(interval);
};
}, [slide]);
const ChangeSlideDots = (index) => {
setSlide(index);
};
const NextSlide = () =>
setSlide((prev) => (slide === images.length - 1 ? 0 : prev + 1));
const PrevSlide = () =>
setSlide((prev) => (slide === 0 ? images.length - 1 : prev - 1));
return (
<div style={styles.root}>
<img style={styles.imageDiv} src={images[slide]} />
<button style={styles.buttons} onClick={PrevSlide}>
◁
</button>
<div style={styles.dotDiv}>
{images.map((_, i) => (
<div
key={i}
style={i === slide ? styles.redDot : styles.blackDot}
onClick={() => ChangeSlideDots(i)}
>
.
</div>
))}
</div>
<button style={styles.buttons} onClick={NextSlide}>
▷
</button>
</div>
);
}
const styles = {
root: {
display: "flex",
position: "relative",
width: 300,
height: 200,
},
buttons: {
backgroundColor: "rgb(255 255 255 / 37%)",
border: "none",
zIndex: 2,
flex: 1,
},
imageDiv: {
position: "absolute",
zIndex: 1,
width: 300,
height: 200,
},
dotDiv: {
flex: 10,
zIndex: 2,
fontSize: "30px",
display: "flex",
justifyContent: "center",
},
redDot: {
cursor: "pointer",
color: "red",
},
blackDot: {
cursor: "pointer",
color: "black",
},
};
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Anytime that you rerender your component, you will run the whole function once. So you will set an interval every time you use setImage(). In order to prevent this, you have to use side effect functions. here you should use useEffect() because you have a functional component. in order to make useEffect() only run once, you have to pass an empty array for dependecy array; So your useEffect will act like componentDidMount() in class components. try the code below:
let interval = null
useEffect(() => {
interval = setInterval(
() => (image === 4 ? setImage(1) : setImage(image + 1)),
5000
)
setTimeout(() => {
clearInterval(interval);
}, 5000)
}, [])
Thanks, everybody for your great answers appreciated a lot your time and help!
So, my final solution looks like this:
const images = [image1, image2, image3, image4];
const quotes = [
'Jobs fill your pockets, adventures fill your soul',
'Travel is the only thing you buy that makes you richer',
'Work, Travel, Save, Repeat',
'Once a year, go someplace you’ve never been before',
];
const App = () => {
//Image is displayed
const [image, setImage] = React.useState(0);
// Auto change slide interval
useEffect(() => {
return () => {
clearInterval(
setInterval((interval) => {
image === 3 ? setImage(1) : setImage(image + 1);
clearInterval(interval.current);
}, 5000)
);
};
}, [image]);
// Change image functionality
const ChangeImage = (index) => {
setImage(index);
};
//Next image
const NextImage = () => {
image === 3 ? setImage(1) : setImage(image + 1);
};
// Previous image
const PrevImage = () => {
image === 1 ? setImage(3) : setImage(image - 1);
};
return (
<Section>
<div className='slideshow-container'>
<div>
<img className='slider_image' src={images[image]} alt='slider' />
<h1 className='slider_title'>{quotes[image]}</h1>
</div>
<button className='slider_prev' onClick={PrevImage}>
❮
</button>
<button className='slider_next' onClick={NextImage}>
❯
</button>
</div>
<div>
<div>
{images.map((image, i) => (
<img
key={i}
alt={`slider${i}`}
src={image}
className='bottom_image'
onClick={() => ChangeImage(i)}
></img>
))}
</div>
</div>
</Section>
);
};

FlatList not showing data correctly after data changes

I'm getting the data from the database and show it in a FlatList. Whenever I add or remove something from the data the data isn't showing correctly in the FlatList.
Whenever I remove something it shows an empty list.
Whenever I add something it only shows the newly added data - nothing else.
I'm using firebase realtime database and use the data I get as follows:
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
const val = snap.val().words;
const data = [];
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords(data);
// setWords([...data]) doesn't work either.
}
})
My Flatlist looks like this:
<FlatList
data={words}
renderItem={renderItem}
keyExtractor={item => item.key}
extraData={words}
/>
When I console.log() the data I always get the data I want to show but the FlatList just won't show it correctly.
It also doesn't work when I use the spread-operator and/or extraData.
Because someone asked for it here is the entire file (I left out the styling and the imports)
const EditList = ({ editKey }) => {
const [wordlist, setWordlist] = useState(0);
const [refresh, setRefresh] = useState(false);
const [words, setWords] = useState([]);
const [wordLoading, setWordLoading] = useState({ loading: false });
const [loading, setIsLoading] = useState(false);
const [btnLoading, setBtnLoading] = useState(false);
const [word, setWord] = useState('');
useEffect(() => {
if (editKey !== 0) {
setIsLoading(true);
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
setWordlist({...snap.val()});
const val = snap.val().words;
const data = [];
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords([...data]);
setRefresh(!refresh);
console.log(data, 'DATA');
}
})
}
}, [editKey])
const onAdd = () => {
setBtnLoading(true);
firebase.database().ref(`/wordlists/${editKey}/words`).push({ word })
.then(() => {
setBtnLoading(false);
setWord('');
setRefresh(!refresh);
})
}
const onDelete = (key) => {
setWordLoading({ key, loading: true });
firebase.database().ref(`/wordlists/${editKey}/words/${key}`).remove().then(() => {
setWordLoading({ loading: false });
setRefresh(!refresh);
});
}
const renderItem = ({ item }) => (
<ItemWrapper>
<ItemWord>{ item.word }</ItemWord>
<DeleteView onPress={() => onDelete(item.key)}>
{ wordLoading.loading && wordLoading.key === item.key ?
<ActivityIndicator size="small" /> :
<DIcon name="trash-2" size={24} />
}
</DeleteView>
</ItemWrapper>
)
const createData = (words) => {
const data = [];
if (typeof words !== 'undefined') {
Object.keys(words).forEach(key => {
const obj = { key, word: words[key].word };
data.push(obj);
})
}
console.log(data, 'DATADATADATA');
return data;
}
if (editKey === 0) {
return (
<NokeyWrapper>
<NoKeyText>No list selected...</NoKeyText>
</NokeyWrapper>
)
}
if (loading) {
return (
<NokeyWrapper>
<ActivityIndicator size="large" />
</NokeyWrapper>
)
}
return (
<Wrapper
behavior={Platform.OS == "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === 'ios' && 180}
>
<WordListName>{wordlist.listName}</WordListName>
<FlatListWrapper>
<FlatList
data={words}
renderItem={renderItem}
keyExtractor={item => item.key}
//extraData={refresh}
extraData={words}
/>
</FlatListWrapper>
<AddWordWrapper>
<SInput value={word} onChangeText={(text) => setWord(text)} />
<Button onPress={() => onAdd()} loading={btnLoading}>
<Feather name="plus" size={24} color="black" />
</Button>
</AddWordWrapper>
</Wrapper>
)
};
export default EditList;
u need to useRef for this instance because the new 'words' is not inside the .on('value') call.
const [words, _setWords] = useState([]);
const wordRef = useRef(words)
//function to update both wordRef and words state
const setWords = (word) => {
wordRef = word
_setWords(word)
}
useEffect(() => {
if (editKey !== 0) {
setIsLoading(true);
let data = wordRef //create a temp data variable
firebase.database().ref(`/wordlists/${editKey}`).on('value', snap => {
if (snap.val() !== null) {
setIsLoading(false);
setWordlist({...snap.val()});
const val = snap.val().words;
Object.keys(val).forEach(key => {
data.push({ key, word: val[key].word });
})
setWords(data);
setRefresh(!refresh);
console.log(data, 'DATA');
}
})
return () => firebase.database().ref(`/wordlists/${editKey}`).off('value') // <-- need to turn it off.
}
}, [editKey, wordRef])
You probably need to change setRefresh etc with the same method if they are not refreshing.
After a lot more tries I found out the problem was somewhere else. Somehow using 'flex: 1' on my in my renderItem() was causing this issue. I actually found this issue also on github: GitHub React Native issues
So after removing 'flex: 1' from the element everything was showing as expected.
// before
const renderItem = ({ item }) => (
<ItemWrapper style={{ flex: 1, flexDirection: row }}>
<ItemWord>{ item.word }</ItemWord>
</ItemWrapper>
)
// after
const renderItem = ({ item }) => (
<ItemWrapper style={{ width: '100%' }}>
<ItemWord>{ item.word }</ItemWord>
</ItemWrapper>
)

React Native hooks, useRef and useEffect

I'm trying to translate a Class Component into a Functional one with React Native.
My Search component lets the user search for a film name and I'm making an API call to show him all corresponding films.
Here is my class component :
class Search extends React.Component {
constructor(props) {
super(props);
this.searchedText = "";
this.page = 0
this.totalPages = 0
this.state = {
films: [],
isLoading: false
}
}
_loadFilms() {
if (this.searchedText.length > 0) {
this.setState({ isLoading: true })
getFilmsFromApiWithSearchedText(this.searchedText, this.page+1).then(data => {
this.page = data.page
this.totalPages = data.total_pages
this.setState({
films: [ ...this.state.films, ...data.results ],
isLoading: false
})
})
}
}
_searchTextInputChanged(text) {
this.searchedText = text
}
_searchFilms() {
this.page = 0
this.totalPages = 0
this.setState({
films: [],
}, () => {
this._loadFilms()
})
}
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._searchFilms()}
/>
<Button title='Rechercher' onPress={() => this._searchFilms()}/>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (this.page < this.totalPages) {
this._loadFilms()
}
}}
/>
</View>
)
}
}
render() {
return (
<View>
<TextInput
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._searchFilms()}
/>
<Button title='Search' onPress={() => this._searchFilms()}/>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
onEndReachedThreshold={0.5}
onEndReached={() => {
if (this.page < this.totalPages) {
this._loadFilms()
}
}}
/>
{this._displayLoading()}
</View>
)
}
}
How can I translate the following with hooks :
this.page and this.totalPages ? is useRef the solution ?
in _searchFilms() I'm using setState callback to make a new API call when my film list is empty (because it's a new search). But doing it right after doesn't work because setState is asynchronous.
But I can't find a way to do it with hooks.
I think useEffect could do this but :
I only want to make this API call when my film list is empty, because I call _searchFilms() for a new search.
_loadFilms() is called on user scroll to add more films to the FlatList (for the same search) so I can't clear this.films in this case.
Here is how I translated it so far :
const Search = () => {
const [searchText, setSearchText] = useState('');
const [films, setFilms] = useState([]);
// handle pagination
const page = useRef(0);
const totalPages = useRef(0);
// handle api fetch
const [isLoading, setIsLoading] = useState(false);
const loadFilmsFromApi = () => {
getFilmsFromApiWithSearchedText(searchText, page + 1).then((data) => {
page.current = data.page;
totalPages.current = data.total_pages;
setFilms(films => [...films, ...data.results]);
setIsLoading(false);
})
};
const searchFilm = () => {
if (searchText.length > 0) {
page.current = 0;
totalPages.current = 0;
setFilms([]);
// HERE MY Films list won't be cleared (setState asynchronous)
loadFilmsFromApi();
// after the api call, clear input
setSearchText('');
}
};
useEffect(() => {
console.log(page, totalPages, "Film number" + films.length);
}, [films]);
I think you are on the right path. As for totalPages and page, having it as a ref makes sense if you want to maintain that values between different renders ( when setting state )
const Search = () => {
const [searchText, setSearchText] = useState('');
const [films, setFilms] = useState([]);
// handle pagination
const page = useRef(0);
const totalPages = useRef(0);
// handle api fetch
const [isLoading, setIsLoading] = useState(false);
// This can be invoked by either search or user scroll
// When pageNum is undefined, it means it is triggered by search
const loadFilmsFromApi = (pageNum) => {
console.log("APPEL", 'loadFills');
getFilmsFromApiWithSearchedText(searchText, pageNum ? pageNum + 1 : 1).then((data) => {
page.current = data.page;
totalPages.current = data.total_pages;
setFilms(films => {
if(pageNum) {
return [...films, ...data.results];
} else {
return [data.results];
}
});
setIsLoading(false);
})
};
useEffect(() => {
if (searchText.length > 0) {
page.current = 0;
totalPages.current = 0;
setFilms([]);
loadFilmsFromApi();
// after the api call, clear input
setSearchText('');
}
}, [searchText, loadFilmsFromApi]);
useEffect(() => {
console.log(page, totalPages, "Nombre de film " + films.length);
}, [films]);
return ( <
div > Search < /div>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Not totally clear what your question is, but it sounds like you want to clear films state before you fire off the query to the api? I am also not clear on the use of useRef here - useRef is simply a way to get a reference to an element so it's easy to access it later - like get a reference to a div and be able to access it easily via myDivRef.current
const = myDivRef = useRef;
...
<div ref={myDivRef}/>
If that is the case, then I would simply set the state of films once in the return of the API call. WRT to the refs, it seems like you this should just be normal variables, or possible state items in your function.
UPDATE:
After clearing up the goal here, you could simply add a parameter to loadFilmsFromApi to determine if you should append or overwrite:
const loadFilmsFromApi = (append) => {
getFilmsFromApiWithSearchedText(searchText, page + 1).then((data) => {
page.current = data.page;
totalPages.current = data.total_pages;
if (append) {
setFilms({
films: this.state.films.concat(data.results)
});
} else {
setFilms({
films: data.results
});
}
setIsLoading(false);
})
};

Resources