Asynchronous actions take time. An indeterminate amount of time could theoretically pass. So, there's no way to guarantee that your data fetches before mounting your component.
Instead, you need to act as if the data could be undefined and have a state of the application that handles that.
As for getting access to the pages variable immediately after calling setPages
, that will also fail because React actually runs the re-render asynchronously.
Even if it ran at the same time, there's a thing called closures
in which javascript pulls in all variables around a function when it is created, so that the function always has access to those. React Hooks work by utilizing these closures to only have access to the variables as they were when the component was rendered/re-rendered. The reason this is the case, is because every re-render, all of the functions in your component are re-created which creates a new closure.
So, this means that you'll need to keep these concepts in mind as you work with React.
As for your code, the results solution that Dlucidione set up is one of your best bets, aside from setting up a separate useEffect to update page
when pages
changes.
const history = useHistory(); //from react-router-dom
useEffect(() => {
async function fetchData() {
return await fetch(`https://myBackend/pages`)
.then((response) => response.json())
.then((response) => {
setPages(response);
})
.catch((err) => setErrors(err));
}
fetchData();
return () => {
console.log('unmounting...');
};
}, []);
useEffect(() => {
const url = props.match.params.url;
if (!pages || !url) return;
// Using Array#find here because I assume based on the name, you want one page rather than an array
const page = pages.find((singlepage) => {
return singlepage.url === url;
});
if (!page) {
// This will change the route if a page isn't found.
history.push('/404');
}
setPage(page);
}, [pages, props.match.params.url, history]);
Redirect
and history.push
are different in how they work. Redirect
is the declarative navigation method, while history.push
is the programmatic.
Example usage of history.push
:
if (!page) {
// This will change the route if a page isn't found.
history.push('/404');
}
Important note, this can be used anywhere in the code as long as you can pass the history object to there. I've used it within redux thunks before as well.
Example usages of Redirect
without it redirecting on mount:
if(!pages && !page){
return <Redirect to="/404"/>
}
Or within some JSX:
<div>
{!pages && !page && <Redirect to="/404"/>}
</div>
Redirect has to be rendered which means its only usable within the return statement of a component.
The way I'm understanding it: the redirect is based on if the
individual page is found in the mounting process, therefore the
redirecting process has to also be in the mounting process to check
before rendering anything to redirect or not.
This is correct. When the Redirect itself mounts or updates, it will redirect if the conditions are correct. If there's a path
or from
prop on the Redirect (they are aliases of each other), then that limits the Redirect to only work when that path matches. If the path doesn't match, the redirect will do nothing until the path is changed to match (or it unmounts, of course).
Under the hood, Redirect just calls history.push
or history.replace
in componentDidMount and componentDidUpdate (via the Lifecycle component), so take that as you will.