i created a _app.js file and added this code to it witch should store state of previous url in an array. When trying to access the previous url i just get the current page url.
Is there something wrong in the logic of the _app.js code or in passing the history value to other components/pages?
_app.js
import React from 'react';
import App from 'next/app';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
state = {
history: []
};
componentDidMount() {
const { asPath } = this.props.router;
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
componentDidUpdate() {
const { history } = this.state;
const { asPath } = this.props.router;
if (history[history.length - 1] !== asPath) {
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
}
render() {
const { Component, pageProps } = this.props
return <Component history={this.state.history} {...pageProps} />
}
}
export default MyApp;
Example of passing history
const CatPage = ({ history }) => {
console.log(history) //I get the value of a current page
console.log(history[0]) // same thing it is just the current url in the array
}
Related
I am trying to get the id from the route as http//localhost:3000/portfolios/helloworld so id is helloworld. But i get an error that says TypeError: Cannot destructure property 'id' of 'query' as it is undefined.
const PortfolioDetail = ({ query }) => {
const { id } = query;
return <h1>I am Details Page with ID: {id}</h1>;
};
PortfolioDetail.getInitialProps = ({ query }) => {
return { query };
};
export default PortfolioDetail;
I tried the same thing with class component but the error was same.
// class PortfolioDetail extends React.Component {
// static getInitialProps({ query }) {
// return { query };
// }
// render() {
// const id = this.props.query.id;
// return <h1>I am Detail Page with id : {id} </h1>;
// }
// }
// export default PortfolioDetail;
this is my project structure you can see below image
It only works and i can get my id using useRouter i showed below.
import { useRouter } from 'next/router';
import React from 'react';
const PortfolioDetail = () => {
const router = useRouter();
const id = router.query.id
return <h1>I am Details Page with ID: {id}</h1>;
};
PortfolioDetail.getInitialProps = ({ query }) => {
return { query };
};
export default PortfolioDetail;
I am stuck at this point and i really wanna know why it won't work.
I've got it, you have an error in your _app:
import '../styles/index.scss';
import 'bootstrap/dist/css/bootstrap.min.css';
// Don't need to spread pageProps here
const MyApp = ({ Component, ...pageProps }) => {
return <Component {...pageProps} />;
};
export default MyApp;
It should be:
const MyApp = ({ Component, pageProps }) => {
return <Component {...pageProps} />;
};
Why dont use it like the code shown below
export default function FirstPost({ id }) {
console.log("-->", id);
return (
<>
{id}sdlfdfdlkj
</>
);
}
FirstPost.getInitialProps = ({ query }) => {
return { id: query?.id };
};
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: {
},
};
}
I am trying to do authentication in Nextjs using firebase and redux but I run into this error when I use connect from react-redux.
Note: I'm not a pro so do forgive me if my code seems bad.
import "../styles/global.scss";
import "bootstrap/dist/css/bootstrap.min.css";
import App from "next/app";
import { Provider, connect } from "react-redux";
import withRedux from "next-redux-wrapper";
import { auth, createUserProfileDocument } from "../firebase/firebase.utils";
import store from "../redux/store";
import { setCurrentUser } from "../redux/user/user.actions";
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {};
//Anything returned here can be access by the client
return { pageProps: pageProps };
}
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot((snapshot) => {
setCurrentUser({
id: snapshot.id,
...snapshot.data(),
});
});
} else {
setCurrentUser(userAuth);
}
});
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
//Information that was returned from 'getInitialProps' are stored in the props i.e. pageProps
const { Component, pageProps, store } = this.props;
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
}
//makeStore function that returns a new store for every request
const makeStore = () => store;
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
});
const wrappedApp = withRedux(makeStore)(MyApp);
export default connect(null, mapDispatchToProps)(wrappedApp);
I have a sandbox link here.
Please help
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)
I've used getInitialProps to load a large json file onto an individual page. Since it's a rather large json, I was wondering how I should go about loading it onto the index page to start with. The subpages should load it only if someone goes onto a subpage directly bypassing the index and it's not in the props already. The docs are a bit confusing on loading onto the _app compared to individual components. Also, not clear how to do a check within then getInitialProps if the props are already fetched...
import App from 'next/app'
import React from 'react'
import withReduxStore from '../store/with-redux-store'
import { Provider } from 'react-redux'
import "isomorphic-fetch"
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let res = await fetch('https://xxxx.json', { mode: 'no-cors' });
let productParams = await res.text().then((data) => {
return (data ? JSON.parse(data) : {})
})
.catch((error) => {
console.error("Something bad happened", error);
});
console.log(`Show data fetched. Count: ${Object.keys(productParams).length}`);
return { productParams, topState: "loaded" }
}
render() {
return (
<Provider store={reduxStore}>
<Component {...this.props} />
</Provider>
)
}
}
export default withReduxStore(MyApp)
________________________________________________
class SubPage extends React.Component {
static async getInitialProps({ reduxStore, topState }) {
reduxStore.dispatch(loadInitialState());
if (topState != "loaded") {
let res = await fetch('https://xxxxxx.json', { mode: 'no-cors' })
let productParams = await res.json();
return { productParams }
} else {
return {}
}
}
state = { ...this.props, riskType: "xxx" }
componentDidMount() {
console.log(this.state);
}
render() {
return (
<Layout>
<SubComponent />
</Layout>
)
}
}
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch) => {
return {
loadInitialState: () => {
dispatch({ type: "LOAD_INITIAL_STATE" });
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(SubPage)
If I go to the main page, the _app loads the json, then if I click on the subpage link, its no longer in the props. Only when I reload the subpage, it appears in the props again. What am I doing wrong?
From what I can gather, your example seems to have several issues. But the main ones that would need addressing for your scenario to work are:
Component on _app.js is actually a prop passed to your MyApp component.
Since you are overriding it, you should call App.getInitialProps inside your static MyApp.getInitialProps. That would actually trigger calls to your page's own getInitialProps.
The props returned from getInitialProps in _app.js are sent as pageProps to MyApp.
Putting it all together would look something like this:
import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
// Fetch your json file
const res = await fetch('https://xxxx.json', { mode: 'no-cors' });
const productParams = await res.json();
return { ...appProps, productParams, topState: "loaded" };
}
export default MyApp
Just bear in mind that setting a getInitialProps from your custom App will force every page on your app to be server side rendered and void static optimization, entirely. You can read more about custom App on its official documentation section.