useSession((s) => s.user)
will either return an user object
or null
. On the index page the name of the user should be shown. The page is wrapped in PrivatePageProvider
which will redirect the client if the user object is null. So if the user is null, IndexPage
should not even be rendered, instead the provider returns null and redirects. However this is not working and a TypeError
gets thrown, because IndexPage
tries to access name
of null
. With react-router this approach works, but not with Next. I know I could just use the optional chaining operator, but I want to assume that user
is not null if the page is wrapped in PrivatePageProvider
.
function PrivatePageProvider({ children }) {
const user = useSession((s) => s.user);
const router = useRouter();
useEffect(() => {
if (!user) {
router.push('/auth/login');
}
}, [user, router]);
return !user ? null : children;
}
function IndexPage() {
const user = useSession((s) => s.user);
return (
<PrivatePageProvider>
<h1>Hello {user.name}</h1>
</PrivatePageProvider>
);
}
Error:
TypeError: Cannot read properties of null (reading 'name')
Why is this error showing up even though IndexPage
does not get rendered? How can I get my approach to work without involving the server side?
CodePudding user response:
Why is this error showing up even though IndexPage does not get rendered? > Because it's breaking, you have an error, it won't render.
How can I get my approach to work without involving the server side? > Wrap your code in a useEffect
. useEffect
will only execute client side so anything being rendered server side will just ignore the effect and wait till it's rendered before executing it. That's because any effects will only execute once the DOM has been rendered. If user
is null on the server it will break on user.name
unless you move that to an effect.
Additionally, I'm quite confused by what exactly you're trying to do, why is there a useEffect
in the child component at all, instead you should be doing that in IndexPage
. I'm also not sure what the PrivatePageProvider
is for at all, it would make more sense if the page provider was rendering the index page, the other way around. You also shouldn't pass the user as children, instead pass the data prop and render the user in the child page, then you don't need to get the user again.
That being said, looking at your code, it seems to me like you were trying to do something and then got confused/lost along the way by accidentally swapping around your thinking with the way you're using the page and page provider. Perhaps this is what you wanted:
const PrivatePageProvider = ({children}) => {
const user = useSession((s) => s.user)
const router = useRouter()
useEffect(() => {
if (!user) {
router.push('/auth/login')
}
}, [user])
return !user ? <></> : children
}
const IndexPage = () => {
const user = useSession((s) => s.user)
return <h1>Hello {user.name}</h1>
}
So that you can use it like:
<PrivatePageProvider>
<IndexPage/>
</PrivatePageProvider>
<PrivatePageProvider>
<ExamplePage/>
</PrivatePageProvider>
CodePudding user response:
I don't think this approach will work on react-router either. The error is about the context javascript knows when executing your code. To understand better, the JSX tree you are rendering transpiled to plain js looks like this:
function IndexPage() {
const user = useSession((s) => s.user);
return (React.createElement(PrivatePageProvider, null,
React.createElement("h1", null,
"Hello ",
user.name)));
}
When javascript executes a function, it evaluates the arguments first; in this case, when it sees one of them is trying to access a property on a null object(user.name
) it throws the TypeError without even calling the function.
The situation is different if you pass the whole user object as a prop to another element
function IndexPage() {
const user = useSession((s) => s.user);
return (
<PrivatePageProvider>
<SomeComponent user={user} />
</PrivatePageProvider>
);
}
It transpiles to
function IndexPage() {
const user = useSession((s) => s.user);
return (React.createElement(PrivatePageProvider, null,
React.createElement(SomeComponent, { user: user })));
}
Here javascript has no idea that you will access user.name
inside SomeComponent
, hence it continues with the execution without erroring
I suggest using the Context API to provide a non-null user to the components wrapped in your PrivatePageProvider
const UserContext = React.createContext({});
const useUser = () => {
const { user } = useContext(UserContext);
return user;
};
function PrivatePageProvider({ children }: any) {
const user = useSession((s) => s.user);
...
return !user ? null : (
<UserContext.Provider value={{ user }}>{children}</UserContext.Provider>
);
}
function SomeComponent() {
const user = useUser();
return <h1>{user.name}</h1>;
}
function IndexPage() {
return (
<PrivatePageProvider>
<SomeComponent />
</PrivatePageProvider>
);
}