I'm implementing pagination with React. It generally works well, except one issue.
When I'm changing sorting criteria, I want to reset page to 1st. The problem is, changing the page number triggers data fetch again. So whenever I'm on page 2 or above and change sorting criteria, the data is being fetched twice. Once for the fact of changing the criteria (which trigger resetting the page to 1) and then again, as the page changed to one. Is there any clean way to avoid this clash and make the fetch only happen once?
Here's my simplified code:
import { useState, useEffect } from 'react';
export default function MyComponent() {
const [items, setItems] = useState([]);
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
useEffect(
() => (async () => {
const response = await fetch('...');
const { items } = await response.json();
setItems(items);
})(),
[column, direction, currentPage, perPage]
);
useEffect(
() => setCurrentPage(1), // This triggers the useEffect() above
[column, direction, perPage]
);
return (
// Template code
);
}
How would a React guru do this?
You can add a state like shouldFetch that can be used to conditionally fetch and avoid multiple calls.
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [shouldFetch, setShouldFetch] = useState(true);
useEffect(() => {
(async () => {
if (shouldFetch) {
const response = await sleep(1000);
console.log(response);
// prevent fetch as we want to allow it later
setShouldFetch(false);
}
})();
}, [column, direction, currentPage, perPage, shouldFetch]);
useEffect(() => {
setCurrentPage(1);
// allow fetch
setShouldFetch(true);
}, [column, direction, perPage]);
const changeColumn = () => {
setColumn("new-col");
};
const changeCurrentPage = () => {
setCurrentPage(2);
// to fetch when currentPage changes
// this should not be added to other handlers as it is also present in the second useEffect that gets triggered when other params change
setShouldFetch(true);
};
const changePerPage = () => {
setPerPage(20);
};
const changeDirection = () => {
setDirection("descending");
};
Alternative:
To avoid unnecessary fetching and to make sure that items are fetched using the updated state values, you can remove the second useEffect and reset currentPage when you update other params.
This will only trigger the useEffect once because React will perform both state updates (setColumn and setCurrentPage) at once.
const sleep = (ms) => new Promise((res) => setTimeout(() => res("Hi Mom"), ms));
export default function App() {
// const [items, setItems] = useState([]);
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
useEffect(() => {
(async () => {
const response = await sleep(1000);
console.log(response);
})();
}, [column, direction, currentPage, perPage]);
// remove this effect
// useEffect(() => setCurrentPage(1), [column, direction, perPage]);
const changeColumn = () => {
setColumn("new-col");
setCurrentPage(1);
};
const changeCurrentPage = () => {
setCurrentPage(2);
};
const changePerPage = () => {
setPerPage(20);
setCurrentPage(1);
};
const changeDirection = () => {
setDirection("descending");
setCurrentPage(1);
};
return (
<>
<button onClick={changeColumn}>change column</button>
<button onClick={changeDirection}>change direction</button>
<button onClick={changeCurrentPage}>change page</button>
<button onClick={changePerPage}>change perPage</button>
</>
);
}
Related
I am using:
const useItemsF = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const user = firebase.auth().currentUser;
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection("user")
.where("id", "==", `${user.uid}`)
.onSnapshot(snapshot => {
const listItemsUsers = snapshot.docs.map(doc => ({
ProfilePic: doc.get("userProfilePic")
}));
setItems(listItemsUsers);
});
return () => unsubscribe();
}, []);
return items;
};
By why is it when loading the page that the console for the array returns empty multiple times before showing the full items array?
Is there a way to preload this before it renders the page? Or am I doing something wrong here?
Add a loading indicator to wait until the request back with data
const useItemsF = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const user = firebase.auth().currentUser;
useEffect(() => {
setLoading(true)
const unsubscribe = firebase
.firestore()
.collection("user")
.where("id", "==", `${user.uid}`)
.onSnapshot(snapshot => {
const listItemsUsers = snapshot.docs.map(doc => ({
ProfilePic: doc.get("userProfilePic")
}));
setItems(listItemsUsers);
// data fetched
setLoading(false)
});
return () => unsubscribe();
}, []);
return items;
};
SO this is my code, i'm trying to filter my offers by there users , i have called all my offers and all my user and there states are full but when i try to filter offers by there users the state stay empty but when i hit spacebar on my keyboard the state get full like it's the spacebar is triggering useEffect to fill the state
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const[useroffers,setUseroffer]=useState([]);
const isOffer = async () => {
const oflg = await GetAllOff();
setOffer(oflg);
};
const isLoggedIn = async () => {
const userLg = await CurrentUser();
setUser(userLg.data.user);
};
const isUseroffer = async()=>{
setUseroffer(offer.filter((el)=>el.createdbyId === user._id));
};
useEffect( () => {
isOffer();
isLoggedIn();
isUseroffer();
}, []);
console.log(offer);
console.log(user)
console.log(useroffers);
So useEffect is filling the offers and user States but not filling the useroffers state intil i click on the spacebar
useroffers is dependent on both user and offer but you are trying to set it in the same render cycle as those two. Updated state values aren't available until the render cycle after they are set, so setUseroffers doesn't have access to the values it needs to update properly. To solve this you can declare a second useEffect which is dependent on user and offer so that as those values update so does your filtered array.
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const [useroffers, setUseroffer] = useState([]);
const isOffer = async () => {
const oflg = await GetAllOff();
setOffer(oflg);
};
const isLoggedIn = async () => {
const userLg = await CurrentUser();
setUser(userLg.data.user);
};
useEffect(() => {
isOffer();
isLoggedIn();
}, []);
useEffect(() => {
setUseroffer(offer.filter((el) => el.createdbyId === user._id));
}, [user, offer]);
codesandbox
Alternatively you can do it all in a single useEffect by awaiting the offer and user values and using them directly to set all three states once they are available. (This will result in only a single rerender rather than the possible four in the previous example)
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const [useroffers, setUseroffer] = useState([]);
useEffect(() => {
const login = async () => {
const userLg = await CurrentUser();
const ofLg = await GetAllOff();
setUser(userLg.data.user);
setOffer(ofLg);
setUseroffer(
ofLg.filter((el) => el.createdbyId === userLg.data.user._id)
);
};
login();
}, []);
codesandbox
When I'm adding dependency fetchData, my app becomes an infinite loop.
What am I doing wrong?
React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array
const [films, setFilms] = useState([]);
const [page, setPage] = useState(1);
const [isLoad, setIsLoad] = useState(false);
const incrementPage = () => {
setPage(page + 1);
};
const fetchData = async () => {
setIsLoad(true);
const response = await fetch(
`${baseURL}movie/popular?api_key=${API_KEY}&language=en-US&page=${page}`
).then((res) => res.json());
setFilms([...films, ...response.results]);
setIsLoad(false);
incrementPage();
};
useEffect(() => {
fetchData();
}, []);
I would place the contents of fetchData into the useEffect instead.
const [films, setFilms] = useState([]);
const [page, setPage] = useState(1);
const [isLoad, setIsLoad] = useState(false);
const incrementPage = () => {
setPage(page + 1);
};
useEffect(() => {
setIsLoad(true);
const response = await fetch(
`${baseURL}movie/popular?api_key=${API_KEY}&language=en-US&page=${page}`
).then((res) => res.json());
setFilms([...films, ...response.results]);
setIsLoad(false);
incrementPage();
}, [setIsLoad, page, setFilms, setIsLoad, incrementPage]);
Then it will automatically fetch new data if 'page' is changed.
I am trying to handle the data sent via onChange in order to make api calls based on the dropdown options , but I want to handle a default value if there is nothing sent to the api call
async function fetchFeed(domain) {
return api.get(`http://localhost:8002/api/v1/xxx/list/?domain=${domain}`);
}
export default function Board() {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const [responseData, setResponseData] = useState([]);
// fetches data
const fetchData = (domain) => {
fetchFeed(domain)
.then((response) => {
setResponseData(response.data.results);
})
.catch((error) => {
console.log(error);
});
};
const handleChange = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
const domain = ??
fetchData(domain);
}, []);
i got an async call being made in a reducer outside the use effect,
when i switch to the home page from the app page i get a memory leak,
The Call is being made in a different file.
any suggestions on a fix?
The Call:
export const query = createAsyncThunk('loadToys', () =>
axios.get(baseUrl)
.then(ok => ok.data)
.catch(err => err),
);
The App:
const toysFromDb = useSelector(CurrToys)
const [localStateToys, setToys] = useState([])
const dispatch = useDispatch();
const mountedRef = useRef(true)
useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
dispatch(query()).then(data => {
setToys(toysFromDb)
})
}
return () => isSubscribed = false
}, [localStateToys])
Solved This:
all i needed to do was the following:
const toysFromDb = useSelector(CurrToys);
const dispatch = useDispatch();
useEffect(() => {
// call the action that will update the store
dispatch(query());
}, []);