Gatsby - location.state 'Cannot read properties of null' - javascript

When I navigate from /pageA to /pageB I am passing a prop through Gatsby's Link to render UI information on PageB that I need from PageA.
If the user goes through the flow properly then the application works as expected. The problem happens when the user manually jumps to /pageB from a different page (i.e: changing url from /home to /pageB)
The navigation library that I'm using is gatsby-plugin-transition-link and I am passing the props following the documentation like so
<Link
to={'/pageB'}
state={{ fromPageA: true }}
>
My initial idea was if the prop returned null, then navigate back to the previous page cause then we know it didn't come from the correct page.
useEffect(() => {
console.log(location.state.fromPageA);
if (location.state.fromPageA === null) {
navigate('/pageA')
}
}, []);
The problem is that I can't even check for that because if I console.log it and it's null it immediately crashes my application with the error
Cannot read properties of null (reading 'fromPageA')

I think what is breaking your current code is the console.log validation, not the if statement. This should work:
useEffect(() => {
if (location?.state?.fromPageA === null) {
navigate('/pageA')
}
}, []);
If you are not using the optional chaining, you can do:
useEffect(() => {
if (location){
if(location.state){
if(!location.state.fromPageA){
navigate('/pageA')
}
}
}
}, []);
Assuming you are destructuring the location as props in the component constructor. Otherwise, use props.location.

Related

NextJs Link causes Error: Loading initial props cancelled

I have nextjs 13 app and I have navbar component with some Nextjs links. After pressing the link and the page starts loading if you click the same link fast some times it throws this error: Error: Loading initial props cancelled.
What I did:
//navbar.ts
const [loadingPath, setLoadingPath] = useState()
useEffect(() => {
Router.events.on('routeChangeComplete', () => setLoadingPath(undefined))
}, [])
const onClickExample= (e) => {
e.preventDefault()
setLoadingPath('/example')
if (loadingPath === '/example' || router.asPath === '/example') return
router.push('/example')
}
//jsx
<Link href="/example" onClick={onClickExample}>Go to Example</Link > // Nextjs Link
There are some discussians like this github issue. It happens on Nextjs 12 too according comments around the net.
This is common user behavior and it's weird to throws such errors.
How do you handle it? Any elegant solution?
I don't see any reason to use onClick in your <Link> here the property href="/example" will make you navigate to /example.
and if you are doing this just to prevent the error then what you are trying to do will not help :
setLoadingPath('/example')
if (loadingPath === '/example')
makes no sens, the loadingPath state will not be updated untill the next render.
This is happening when you try to navigate to another page while the current one is still loading, usually because of a slow connection there are some althernatives like :
const [navigation,setNavigation] = useState('')
//...
const onClickExample = (e) => {
e.preventDefault();
setNavigation("/example"); // when the user clicks you update 'navigation' to the wanted URL to trigger a render
};
//...
useEffect(() => {
if (navigation !== "") { // prevent navigation for the first render
setTimeout(() => {
router.push(navigation); // when the component finishes rendering useEffect runs because navigation is updated then navigate to the wanted URL
}, "500");
}
}, [navigation]);
But you cannot do this with <Link> because it requires href so it will navigate to the given path anyway.
<a onClick={onClickExample}>Go to Example</a>
Try to do it without setTimeout maybe it works for you. In my case it was happening only in development.

How do I resolve Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop while setting a state?

I am getting an object location from another page like this.
const location = useLocation();
I am trying to set a variable like this.
if (location.state !== null) {
console.log(location.state.name.companyName)
setcompanyOneAlready(location.state.name.companyName);
console.log(companyOneAlready);
}
But this results in a "Too many renders" error. I realize it's because of the way I am setting companyOneAlready but I don't know how to correct this. Any help is appreciated.
It seems you are running the code as an unintentional side effect and the condition is malformed.
Use the useEffect hook with correct dependencies to issue the side-effect to update state.
Example:
useEffect(() => {
if (location.state !== null) {
setcompanyOneAlready(location.state.name.companyName);
}
}, [location.state]);
To help save a rerender you can also check the current companyOneAlready value prior to enqueueing the state update.
useEffect(() => {
if (location.state?.name?.companyName !== companyOneAlready) {
setcompanyOneAlready(location.state.name.companyName);
}
}, [location.state, companyOneAlready]);
It seems like location.state is never null. I believe by default it is an empty object and {} !== null is always true. Also, I assume that setcompanyOneAlready changes the state in the component. That triggers rerender and your's condition will be true on every render. That's the reason why you get the mentioned error.
You should put this condition inside useEffect and useEffect should have a name or companyName in the dependency array to rerun the effect only when some of these values are changed.

Filtering Table data with useEffect Hook causing component to re-render infinitely

I'm trying to use a search bar component to dynamically filter the content of a table that's being populated by API requests, however when I use this implementation the component re-renders infinitely and repeatedly sends the same API requests.
The useEffect() Hook:
React.useEffect(() => {
const filteredRows = rows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
});
if (filteredRows !== rows){
setRows(filteredRows);
}
}, [rows, search]);
Is there something I've missed in this implementation that would cause this to re-render infinitely?
Edit 1:
For further context, adding in relevant segments of code from that reference this component which might cause the same behaviour.
Function inside the parent component that renders the table which calls my API through a webHelpers library I wrote to ease API request use.
function fetchUsers() {
webHelpers.get('/api/workers', environment, "api", token, (data: any) => {
if (data == undefined || data == null || data.status != undefined) {
console.log('bad fetch call');
}
else {
setLoaded(true);
setUsers(data);
console.log(users);
}
});
}
fetchUsers();
Edit 2:
Steps taken so far to attempt to fix this issue, edited the hook according to comments:
React.useEffect(() => {
setRows((oldRows) => oldRows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
}));
}, [search]);
Edit 3:
Solution found, I've marked the answer by #Dharmik pointing out how Effect calls are managed as this caused me to investigate the parent components and find out what was causing the component to re-render repeatedly. As it turns out, there was a useEffect hook running repeatedly by a parent element which re-rendered the page and caused a loop of renders and API calls. My solution was to remove this hook and the sub-components continued rendering as they should without loops.
It is happening because you've added rows to useEffect dependency array and when someone enters something into search bar, The rows get filtered and rows are constantly updating.
And because of that useEffect is getting called again and again. Remove rows from the useEffect dependency array and it should work fine.
I would like to complement Dharmik answer. Dependencies should stay exhaustive (React team recomendation). I think a mistake is that filteredRows !== rows uses reference equality. But rows.filter(...) returns a new reference. So you can use some kind of deep equality check or in my opinion better somethink like:
React.useEffect(() => {
setRows((oldRows) => oldRows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
}));
}, [search]);

unable to access vuex store property in safari. code is working fine in Chrome

I am desperately needing some help with vuejs vuex store. it is working absolutely fine in chrome but in safari store property is not being picked up. basically I have stored a user object in Vuex store as follows: -
here is the image of my code
here is the image of within component where I am trying to access the property from store
here is the image of when axios call is made and within response I am triggering the mutation
state:{
user:{},
student:{}
},
mutations:{
setUser(state,payload) {
state.user = payload;
},
setLoggedIn (state,payload) {
state.isLoggedIn = payload;
},
setStudent(state,payload){
state.student = payload;
}
},
getters:{
isStudent: state => {
if(state.user.user_type === 'student') {
return true;
}
}
}
I am calling the mutation from inside the login component whilst executing axios call request and setting the user and student through appropriate commit from the component.
However, when I am trying to access the user property inside the component, it works fine for Chrome but it is not working for Safari. Here is how I am calling for state user property inside the component: -
computed:{
computedStatus(){
return this.$store.state.isLoggedIn;
},
computedUserType(){
return this.$store.state.user.user_type;
},
isUserStudent(){
return this.$store.getters.isStudent;
}
}
The property isUserStudent is used inside the component and is a dependency to further functionalities. This property is assessed correctly and as expected inside Chrome/Firefox, but in safari it is not being assessed. I am really puzzled what's going on here and hope that someone can support please

Why is my component that receives props not working when I destructure out the properties, but when I use props.key it's working?

The Problem
I have an application that uses this React Redux Boilerplate: https://github.com/flexdinesh/react-redux-boilerplate
I created a new page that is connected to the injected reducer + saga.
I receive following props: posts, loading, error, loadPosts and match
When I use these directly the app is working as expected. But as soon as I start to destructure the props, the app is behaving unexpectedly.
Especially with the match props.
When I do it like this:
const SubforumPage = (props) => {
useEffect(() => {
const { id: subId } = props.match.params;
console.log('props: ', subId);
}, []);
// .... other code
}
No problem everything works.
But when I do it like this:
const SubforumPage = ({match}) => {
useEffect(() => {
const { id: subId } = match.params;
console.log('props: ', subId);
}, []);
// .... other code
}
match suddenly gets undefined!
I have really no clue what so ever why this is happening. It's the first time that I see an error like this.
This specific page is set up like this in the routing file:
<Route path="/sub/:id" component={SubforumPage} />
And it's clearly working when using (props) in the function arguments but not with ({match})
Why is this? Can please someone help me out here.
What have I tried?
I continuesly started destructuring one prop after another. At first this approach works and it's still not undefined but when I get to some props, it's different which ones, it will stop working.
I think it has to do something with how I use my useEffect() hook?
I pass an empty array so it does just run when mounting. It seems like when I refresh the page, the posts are cleared out but the useEffect doesn't run anymore, so the new posts doesn't get fetched. Because hen also the console.log inside the useEffect hook is undefined doesn't even run. But for example the loading prop in console.log outside of useEffect is indeed not undefined
(But that still does not explain why it's working with (props) as argument).
Am I just using useEffect wrong?
Many thanks
Ok guys that was completely my fault. Guess I'm too tired :D. Here is what caused the problem:
I fetch my post in the useEffect hook. I also render a component where I pass in the posts. But the posts are not available because the component has to wait for the data to come in. So I completely forgot that I have to wait for the data.
Before:
return <PostsGroup posts={posts} />;
After: (correct)
return <PostsGroup posts={posts || []} />;
I had a check in place looking like this:
if (loading) return <CircularProgress />;
(before the other return). But it doesn't matter because loading is false when the component initially renders.
So I also set the initial value from loading to true (in my initialState of the reducer). So I have now two checks in place.
Sorry guys. So stupid.

Categories