Nextjs not server side rendered when i wrapped component by auth - reactjs

I using Next.js for SSR, after I try to make authentication for user client by Auth component following to my idea. But I have a problem when View Source page, it not loaded as HTML tag which empty root like create-react-app, now it my code in _app.js:
function MyApp({ Component, pageProps: { ...pageProps } }) {
const { auth } = Component;
return (
<>
{
auth ?
<Auth>
<Component {...pageProps} />
</Auth>
:
<Component {...pageProps} />
}
</>
)
}
Which Auth component is:
export const Auth = async (props) => {
const { children } = props;
const auth = await POST('http://localhost:7000/services/api/auth');
if (!auth) {
return <div>Loading...</div>
}
return children
}
And the page wrapped by auth component is:
export const getServerSideProps = async ctx =>{
const { id } = ctx.query;
try {
const postById = await GET(`http://localhost:7000/services/api/post/${id}`);
return {
props: {
post: postById
},
}
}
catch (err) {
console.log(err)
}
}
export default const DetailPost = (props) => {
return (
...
)
}
What should I do it for rendered with HTML DOM like server-side-rendering?

Related

Using result of useSelector() in component

I am using react-redux to manage state and axios to make API calls.
My default state:
const defaultState = {
isLoading: true,
isAuthenticated: false,
authUser: {}
}
isLoading and authUser are both updated once the API's login call has ran successfully. Nothing wrong here.
In a userProfile component, I am using the following to get the logged in user's ID from the store:
const authUser = useSelector(state => state.authentication.authUser);
const user = users.getUser(authUser.id);
console.log(authUser);
authUser is always empty on page load but a split second late, it prints again, this time with the state contents.
I have the userProfile component behind a custom PrivateRoute component:
const PrivateRoute = ({ component: Component, ...rest }) => {
const authentication = useSelector(state => state.authentication);
return (
<Route
{...rest}
render={props =>
authentication.isAuthenticated ? (
<Component {...props} />
) : (
authentication.isLoading ? 'loading...' :
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
)
};
The loading... text shows before the component catches up, then the 2 console logs appear one after the other. The first empty, the second with data.
What am I missing here? This async stuff is driving me insane!
with this implementation of private route u can save current path of user and let user continue his process after login again
import React from 'react';
import { Login } from '../../pageComponents';
import { useSelector } from 'react-redux';
const PrivateRoute = (Component) => {
const Auth = (props) => {
const { isLoggedIn } = useSelector((state) => state.user);
// Login data added to props via redux-store (or use react context for example)
// If user is not logged in, return login componentc
if (!isLoggedIn) {
return <Login />;
}
// If user is logged in, return original component
return <Component {...props} />;
};
// Copy getInitial props so it will run as well
if (Component.getInitialProps) {
Auth.getInitialProps = Component.getInitialProps;
}
return Auth;
};
export default PrivateRoute;
then user it every page you want Private Rout like below
const myPrivateRoute = () => {
return (
<h1>Hello world</h1>
)
}
export default PrivateRoute(myPrivateRoute)
so create component like this and load your last section from local storage or cookie then validate and send into your redux store then you can memorize log
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from "axios";
import * as globalActions from "../../../store/actions/globalAction";
import * as userAction from "../../../store/actions/userAction";
const StorageManagment = () => {
const dispatch = useDispatch();
/* ----------------------------------- listening to token changes ----------------------------------- */
useEffect(() => {
dispatch(userAction.loadCart());
}, []);
useEffect(async () => {
try {
if (window)
await checkLocalDataWithRedux()
.then((data) => {
const { userDTO, token } = data;
dispatch(userAction.loginUser(token, userDTO));
})
.catch((e) => {
console.log(e);
dispatch(userAction.logOutUser());
});
} catch (e) {
throw e;
}
}, []);
function checkLocalDataWithRedux() {
return new Promise((resolve, reject) => {
try {
/* -------- read user data from localStorage and set into redux store ------- */
const { userDTO, token } = localStorage;
if (userDTO && token) {
resolve({ token: token, userDTO: JSON.parse(userDTO) });
} else logOutUser();
} catch (e) {
reject();
}
});
}
function logOutUser() {
dispatch(userAction.logOutUser());
}
return null;
};
export default StorageManagment;
then load it in your app.jsx file
app.js
render() {
return (
<React.Fragment>
<Provider store={store}>
<ErrorBoundary>
<StorageManagment />
<DefaultLayout>
<Application {...this.props} />
</DefaultLayout>
</ErrorBoundary>
</Provider>
</React.Fragment>
);
}

nextjs 9.4 error on HOC private route with cookies

Trying to implement private ( authenticated ) routes in Nextjs using HOC and cookies but running into error below:
TypeError: Object(...) is not a function
at export default withPrivateRoute(Private);
I have checked elsewhere in the app that cookies are available and also sent with the request. They seem to be available server side.
The HOC at `/components/withPrivateRoute
import { withRouter } from 'next/router';
import { withCookies } from 'react-cookie';
const withPrivateRoute = (authComponent) => {
return class Private extends React.Component {
componentDidMount() {
console.log('PRIVATE ROUTE', this.props);
const { router, cookies } = this.props;
const intendedRoute = router.pathname;
const isAdmin = !!cookies.get('isAdmin');
const isAuthenticated = !!cookies.get('username');
if (!isAuthenticated) {
router.push({
pathname: '/login',
query: { from: intendedRoute },
});
}
if (
isAuthenticated &&
router.pathname.includes('admin') &&
!isAdmin
) {
router.push('/');
}
}
render() {
// eslint-disable-next-line react/jsx-props-no-spreading
return <authComponent {...this.props} />;
}
}
}
export default withCookies(withRouter(withPrivateRoute));
The private route example:
import withPrivateRoute from '../components/withPrivateRoute';
import getCategories from '../lib/getCategories';
const Private = (props) => {
console.log('props', props);
return <div>Private route </div>;
}
export default withPrivateRoute(Private);
export async function getStaticProps() {
let categories = await getCategories();
categories = categories.data.categories;
return {
props: {
categories,
},
};
}
I have since found a better way to handle private routes in Nextjs from this discussion:
Everything is handled inside getServerSideProps, no HOC required.
class Private extends React.Component{
render() {
console.log('props', this.props);
return <p>Private route</p>;
}
}
export default Private;
export async function getServerSideProps(context) {
const { req: { headers, url }, res } = context;
const cookies = {};
if (headers && headers.cookie) {
headers.cookie.split(';').forEach((cookie) => {
const parts = cookie.match(/(.*?)=(.*)$/);
cookies[parts[1].trim()] = (parts[2] || '').trim();
});
}
const isAuthenticated = !!cookies.username;
const isAdmin = !!cookies.isAdmin;
if (!isAuthenticated) {
res.setHeader('Location', `/login?from=${url}`);
res.statusCode = 307;
}
if (
isAuthenticated &&
url.includes('admin') &&
!isAdmin
) {
res.setHeader('Location', '/');
res.statusCode = 307;
}
return {
props: {
},
};
}

Check user on every page using react and redux

In my App.tsx file I have this setup
const App: React.FC = props => {
const [hasRendered, setHasRendered] = useState(false);
const dispatch = useDispatch();
const isAuth = authMiddleWare();
useEffect(() => setHasRendered(true), [hasRendered]);
if (!hasRendered && isAuth !== null) {
if (isAuth) {
dispatch(getUser());
} else {
dispatch(logoutUser());
}
}
...
<PrivateRoute path="/app" component={Dashboard} />
}
const PrivateRoute = ({ component, location, ...rest }: any) => {
const dispatch = useDispatch();
const authenticated = useSelector((state: RootState) => state.user.authenticated);
return <>{authenticated ? React.createElement(component, props) : <Redirect to={{ pathname: LoginRoute, state: { from: props.location } }} </>;
};
However, authentication only gets executed on browser reload, so data is not updated if I click a link within my app. I want the user to be checked when loading any private route.
Create a separate auth component and call it in every route and check the user is authenticated or not.
if the user authenticated then requested route show else return the 404page or homepage.
or check in every page you can check your authentication by putting this code in componentditmount section
import React, { Component } from 'react'
import { connect } from "react-redux";
import { userLoginAction } from "./store/actions/userLogin";
import * as actionType from "./store/actions/actionType";
import { Redirect } from 'react-router-dom';
class componentName extends Component {
constructor(props){
super(props)
this.state={
isMount:false
}
}
componentDidMount(){
const token = localStorage.getItem("token");
if (token != null && this.props.loginStatus === false) {
// this.userLoginData(token)
this.props.auth();
}
this.setState({ isMount: true });
}
render() {
return (
<>
{this.state.isMount?this.props.auth===false?<Redirect from ='/' to='/login' />:
<p>Hello World!</p> :<div>Loging.....</div>
}
</>
)
}
}
const mapGetState = (state) => {
return {
loginStatus: state.usrReducer.login_st,
auth: state.usrReducer.auth,
};
};
const mapDispatchState = (dispatch) => {
return {
login: (data) => dispatch({ type: actionType.LOGIN_ST, payload: data }),
auth: (data) => dispatch(userLoginAction(data)),
};
};
export default connect(mapGetState, mapDispatchState)(componentName)

Send param to fetch in getInitialProps react and nextjs

I 'm traying to send a param to getInitialProp function to made the fecth to the correct json.
here is my code:
hepler.js --> here I made the fetch per se.
export async function getEvents() {
const res = await fetch("https://url/eventos.json");
let new_data = await res.json();
return { events: new_data.data };
}
export async function getDetails(slug) {
const res = await fetch(`https://myurl/${slug}.json`);
let data_detail_event = await res.json();
return { data_detail_event };
}
_app.js // here I have the getInitialProps and works great
import App from "next/app";
import ContextProvider from "../provider/ContextProvider";
import fetch from "isomorphic-unfetch";
import {getEvents, getDetails} from '../helper/index'
export default class MyApp extends App {
static async getInitialProps() {
const events = await getEvents();
return {
events : events.events
};
}
render() {
const { Component, pageProps } = this.props;
return (
<div>
<ContextProvider events={this.props.events} >
<Component {...pageProps} />
</ContextProvider>
</div>
);
}
}
pages/[id].js
import { useRouter } from "next/router";
import Context from "../../config/Context";
/* Components */
import WordCounter from "../../components/word-counter/WordCounter";
function Post(props) {
const router = useRouter();
const context = React.useContext(Context);
return (
<React.Fragment>
<WordCounter />
</React.Fragment>
);
}
Post.getInitialProps = async ({ query}) => {
const detail = await getDetail(query.id) --> here I send the param and it seems never arrive to helper.js, why?
return {detail}
}
export default Post
Where is the problem? HELP!
THAANKS!
i think getInitialProps run in server and your helper function doesn't load there.
use fetch inside getInitialProps .

Cookie token access in _app.js in next.js

Here is my _app.js page:
import { Provider } from 'react-redux';
import App from 'next/app';
import withRedux from 'next-redux-wrapper';
import { Layout } from '../components';
import makeStore from '../store';
import { api } from '../utils/api';
class MyApp extends App {
render() {
const { Component, pageProps, store } = this.props;
console.log(this.props); // Here I cannot see cookie user data, it is empty object even though the cookie is set in `_document.js`
return (
<Provider store={store}>
<Layout { ...this.props }>
<Component { ...pageProps } />
</Layout>
</Provider>
);
}
}
MyApp.getInitialProps = api.authInititalProps();
export default withRedux(makeStore)(MyApp);
As you can see I'm trying to get cookie user data in getInitialProps lifycycle. Here my api file which gathers the logic of getting the cookie data:
const WINDOW_USER_SCRIPT_KEY = '__USER__';
class API {
getServerSideToken = req => {
const { signedCookies = {} } = req;
if(!signedCookies || !signedCookies.token) {
return {}
}
return { user: signedCookies.token };
}
getClientSideToken = () => {
if(typeof window !== 'undefined') {
const user = window[WINDOW_USER_SCRIPT_KEY] || {};
return { user };
}
return { user: {} }
};
authInititalProps = () => ({ req, res }) => {
return req ? this.getServerSideToken(req) : this.getClientSideToken();
}
}
The token itself is set in `_document.js:
import Document, { Head, Main, NextScript } from 'next/document';
import { api } from '../utils/api';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const props = await Document.getInitialProps(ctx);
const userData = await api.getServerSideToken(ctx.req);
return { ...props, ...userData }
}
render() {
const { user = {} } = this.props;
return(
<html>
<Head />
<body>
<Main />
<script dangerouslySetInnerHTML={{ __html: api.getUserScript(user) }} />
<NextScript />
</body>
</html>
);
}
};
export default MyDocument;
What is happening is that I'm not getting the cookie token inside my _app.js, instead I'm getting empty object, even though the cookie is set and in the console after typing window.__USER__ I can see it. The aim of all that is to pass user data down to Layout component which is invoked inside the _app.js. Why cannot I see it inside _app.js?

Resources