I'm integrating NextJS into my React app. I face a problem, on page reload or opening direct link(ex. somehostname.com/clients) my getInitialProps not executes, but if I open this page using <Link> from next/link it works well. I don't really understand why it happens and how to fix it. I have already came throught similar questions, but didn't find any solution which could be suitable for me.
Clients page code:
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ClientsTable } from '../../src/components/ui/tables/client-table';
import AddIcon from '#material-ui/icons/Add';
import Fab from '#material-ui/core/Fab';
import { AddClientModal } from '../../src/components/ui/modals/add-client-modal';
import CircularProgress from '#material-ui/core/CircularProgress';
import { Alert } from '../../src/components/ui/alert';
import { Color } from '#material-ui/lab/Alert';
import { AppState } from '../../src/store/types';
import { thunkAddClient, thunkGetClients } from '../../src/store/thunks/clients';
import { SnackbarOrigin } from '#material-ui/core';
import { IClientsState } from '../../src/store/reducers/clients';
import { NextPage } from 'next';
import { ReduxNextPageContext } from '../index';
import { PageLayout } from '../../src/components/ui/page-layout';
const Clients: NextPage = () => {
const [addClientModalOpened, setAddClientModalOpened] = useState<boolean>(false);
const [alertType, setAlertType] = useState<Color>('error');
const [showAlert, setAlertShow] = useState<boolean>(false);
const alertOrigin: SnackbarOrigin = { vertical: 'top', horizontal: 'center' };
const dispatch = useDispatch();
const { clients, isLoading, hasError, message, success } = useSelector<AppState, IClientsState>(state => state.clients);
useEffect(() => {
if (success) {
handleAddModalClose();
}
}, [success]);
useEffect(() => {
checkAlert();
}, [hasError, success, isLoading]);
function handleAddModalClose(): void {
setAddClientModalOpened(false);
}
function handleAddClient(newClientName: string): void {
dispatch(thunkAddClient(newClientName));
}
function checkAlert() {
if (!isLoading && hasError) {
setAlertType('error');
setAlertShow(true);
} else if (!isLoading && success) {
setAlertType('success');
setAlertShow(true);
} else {
setAlertShow(false);
}
}
return (
<PageLayout>
<div className='clients'>
<h1>Clients</h1>
<div className='clients__add'>
<div className='clients__add-text'>
Add client
</div>
<Fab color='primary' aria-label='add' size='medium' onClick={() => setAddClientModalOpened(true)}>
<AddIcon/>
</Fab>
<AddClientModal
opened={addClientModalOpened}
handleClose={handleAddModalClose}
handleAddClient={handleAddClient}
error={message}
/>
</div>
<Alert
open={showAlert}
message={message}
type={alertType}
origin={alertOrigin}
autoHideDuration={success ? 2500 : null}
/>
{isLoading && <CircularProgress/>}
{!isLoading && <ClientsTable clients={clients}/>}
</div>
</PageLayout>
);
};
Clients.getInitialProps = async ({ store }: ReduxNextPageContext) => {
await store.dispatch(thunkGetClients());
return {};
};
export default Clients;
thunkGetClients()
export function thunkGetClients(): AppThunk {
return async function(dispatch) {
const reqPayload: IFetchParams = {
method: 'GET',
url: '/clients'
};
try {
dispatch(requestAction());
const { clients } = await fetchData(reqPayload);
console.log(clients);
dispatch(getClientsSuccessAction(clients));
} catch (error) {
dispatch(requestFailedAction(error.message));
}
};
}
_app.tsx code
import React from 'react';
import App, { AppContext, AppInitialProps } from 'next/app';
import withRedux from 'next-redux-wrapper';
import { Provider } from 'react-redux';
import { makeStore } from '../../src/store';
import { Store } from 'redux';
import '../../src/sass/app.scss';
import { ThunkDispatch } from 'redux-thunk';
export interface AppStore extends Store {
dispatch: ThunkDispatch<any, any, any>;
}
export interface MyAppProps extends AppInitialProps {
store: AppStore;
}
export default withRedux(makeStore)(
class MyApp extends App<MyAppProps> {
static async getInitialProps({
Component,
ctx
}: AppContext): Promise<AppInitialProps> {
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx)
: {};
return { pageProps };
}
render() {
const { Component, pageProps, store } = this.props;
return (
<>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</>
);
}
}
);
Looking for your advices and help. Unfortunately, I couldn't find solution by myself.
This is the way Next.js works, it runs getInitialProps on first page load (reload or external link) in the server, and rest of pages that where navigated to with Link it will run this method on client.
The reason for this is to allow Next.js sites to have "native" SEO version.
Related
I have a next js project that is created by create next app and modified _app.ts to this
import "../../public/assets/css/bootstrap.css";
import "antd/dist/antd.css";
import "../../public/assets/css/common.css";
import "../styles/globals.css";
import "../styles/builderGlobals.css";
import "../../public/assets/css/quiz.css";
import "../../public/assets/css/main.css";
import "../../public/assets/css/responsive.css";
import Head from "next/head";
import { wrapper } from "../app/store";
import { setUser } from "../Modules/Auth/authSlice";
import Layout from "../components/Layouts/layout";
import type { AppPropsWithLayout } from "../utils/types";
import { setNotifSettingsData } from "../Modules/Notifications/notificationsSlice";
import serverApi from "../utils/axios/serverApi";
import NextNProgress from "nextjs-progressbar";
import { useAppSelector } from "../app/hooks";
const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
const favicon = useAppSelector(state => state.settingsData.favico_icon);
const getLayout =
Component.getLayout ??
((page) => (
<Layout>
<Head>
<link rel="shortcut icon" href={favicon || "/img/favicon.png"} />
</Head>
<NextNProgress /> {page}
</Layout>
));
return getLayout(<Component {...pageProps} />);
};
MyApp.getInitialProps = wrapper.getInitialAppProps(
(store) =>
async ({ Component, ctx }) => {
if (!ctx.req) {
return {
pageProps: {
...(Component.getInitialProps
? await Component.getInitialProps({ ...ctx, store })
: {}),
pathname: ctx.pathname,
},
};
}
try {
const { data: initialData } = await serverApi(
ctx,
`/settings/get-initial-site-data`
);
store.dispatch(setUser(initialData?.authUser));
store.dispatch(setNotifSettingsData(initialData?.siteSettings));
return {
pageProps: {
...(Component.getInitialProps
? await Component.getInitialProps({ ...ctx, store })
: {}),
pathname: ctx.pathname,
},
};
} catch (error) {
ctx.res.statusCode = 404;
ctx.res.end("Not found");
return;
}
}
);
export default wrapper.withRedux(MyApp);
But after running yarn build it created too large _app chunk. about 431kb
That is huge. How can I reduce this chunk? or am I doing anything wrong?
https://github.com/sakib412/sakib412/raw/main/WhatsApp%20Image%202022-10-13%20at%206.48.08%20PM.jpeg
I would like to open and close an antd Modal component using a MobX store. I have the following code Here's a link to codesandbox https://codesandbox.io/s/nifty-dijkstra-g0kzs6?file=/src/App.js
import AppNavigation from "./components/Menu";
import ContactPopUp from "./components/Contact";
export default function App() {
return (
<div className="App">
<AppNavigation />
<ContactPopUp />
</div>
);
}
File with the MobXstore
import { createContext, useContext } from "react";
import AppStore from "./appStore";
interface Store {
appStore: AppStore;
}
export const store: Store = {
appStore: new AppStore()
};
export const StoreContext = createContext(store);
export function useStore() {
return useContext(StoreContext);
}
Separate file where I declare the store
import { makeAutoObservable } from "mobx";
export default class AppStore {
contactFormOpen = false;
constructor() {
makeAutoObservable(this);
}
setContactFormOpen = (isOpen: boolean) => {
console.log("Changed contact form to ", isOpen);
this.contactFormOpen = isOpen;
};
}
The Menu.tsx
import React from "react";
import { Menu, MenuProps } from "antd";
import { useStore } from "../store/store";
const AppNavigation = () => {
const { appStore } = useStore();
const menuItems: MenuProps["items"] = [
{
label: <a onClick={(e) => handleOpenContactForm(e)}>Contact</a>,
key: "Contact"
}
];
const handleOpenContactForm = (e: any) => {
e.preventDefault();
e.stopPropagation();
appStore.setContactFormOpen(true);
console.log("Open contact pop up", appStore.contactFormOpen);
};
return (
<Menu
items={menuItems}
theme="dark"
overflowedIndicator={""}
className="header__menu award-menu header__menu--md"
/>
);
};
export default AppNavigation;
ContactPopUp.tsx
import { Modal } from "antd";
import React, { useEffect, useState } from "react";
import { useStore } from "../store/store";
const ContactPopUp = () => {
const { appStore } = useStore();
const [visible, setVisible] = useState(appStore.contactFormOpen);
useEffect(() => {
setVisible(appStore.contactFormOpen);
}, [appStore.contactFormOpen]);
const handleCancel = () => {
appStore.setContactFormOpen(false);
console.log("Close contact from", appStore.contactFormOpen);
};
return (
<Modal title="Contact us" visible={visible} onCancel={handleCancel}>
<h2>Modal Open</h2>
</Modal>
);
};
export default ContactPopUp;
The mobx contactFormOpen clearly changes but the modal state does not. I really don't understand why... UseEffect also doesn't trigger a re render.
You just forgot most crucial part - every component that uses any observable value needs to be wrapped with observer decorator! Like that:
const ContactPopUp = () => {
const { appStore } = useStore();
const handleCancel = () => {
appStore.setContactFormOpen(false);
console.log('Close contact from', appStore.contactFormOpen);
};
return (
<Modal
title="Contact us"
visible={appStore.contactFormOpen}
onCancel={handleCancel}
>
<h2>Modal Open</h2>
</Modal>
);
};
// Here I've added `observer` decorator/HOC
export default observer(ContactPopUp);
And you don't need useEffect or anything like that now.
Codesandbox
I want add screen loading in next js project. And I tried to do that with the Router component in next/router.
This is my _app.js in next.js project:
import {CookiesProvider} from 'react-cookie';
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import withRedux from 'next-redux-wrapper'
import withReduxSaga from 'next-redux-saga'
import createStore from '../src/redux/store'
import Router from "next/router";
import {Loaded, Loading} from "../src/util/Utils";
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ctx})
}
return {pageProps}
}
render() {
Router.onRouteChangeStart = () => {
Loading()
};
Router.onRouteChangeComplete = () => {
Loaded()
};
Router.onRouteChangeError = () => {
Loaded()
};
const {Component, pageProps, store} = this.props;
return (
<CookiesProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</CookiesProvider>
)
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
This is Loaded() and Loading() functions:
export const Loaded = () => {
setTimeout(() => {
let loading = 'has-loading';
document.body.classList.remove(loading);
}, 100);
};
export const Loading = () => {
let loading = 'has-loading';
document.body.classList.add(loading);
};
The code works well when the project is under development mode. But when the project is built, the loading won't disappear.
Do you know solution of this issue or are you suggesting another solution?
Using apollo client and react hooks you could do as follow.
Example:
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { withApollo } from '../lib/apollo';
import UserCard from '../components/UserCard';
export const USER_INFO_QUERY = gql`
query getUser ($login: String!) {
user(login: $login) {
name
bio
avatarUrl
url
}
}
`;
const Index = () => {
const { query } = useRouter();
const { login = 'default' } = query;
const { loading, error, data } = useQuery(USER_INFO_QUERY, {
variables: { login },
});
if (loading) return 'Loading...'; // Loading component
if (error) return `Error! ${error.message}`; // Error component
const { user } = data;
return (
<UserCard
float
href={user.url}
headerImg="example.jpg"
avatarImg={user.avatarUrl}
name={user.name}
bio={user.bio}
/>
);
};
export default withApollo({ ssr: true })(Index);
More info here: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
I added the following codes to a wrapper component and the problem was resolved.
componentDidMount() {
Loaded();
}
componentWillUnmount() {
Loading();
}
I have two distinct contexts in my application - Language and Currency. Each of these contexts are consumed by two distinct functional components through the useContext hook. When one of the context values change I want React to only invoke the functional component that consumes that context and not the other. However I find that both functional components get invoked when either context values change. How can I prevent this? Even if React doesn't re-render unchanged DOM after reconciliation I would like to prevent actually calling of the functional component itself.In other words how can I memoize each component (or something similar) while still maintaining my code organization (See below)?
LanguageContext.js
import React from 'react';
const LanguageContext = React.createContext({ lang: 'english', changeLang: (lang) => { } });
export { LanguageContext };
CurrencyContext.js
import React from 'react';
const CurrencyContext = React.createContext({ cur: '$', changeCur: (cur) => { } });
export { CurrencyContext };
ContextRoot.js
import React, { useState } from 'react';
import { LanguageContext } from '../context/LanguageContext';
import { CurrencyContext } from '../context/CurrencyContext';
const ContextRoot = (props) => {
const [lang, setLang] = useState('english');
const [cur, setCur] = useState('$');
const changeLang = (lang) => {
setLang(lang);
}
const changeCur = (cur) => {
setCur(cur);
}
const langCtx = {
lang,
changeLang
};
const curCtx = {
cur,
changeCur
};
return (
<LanguageContext.Provider value={langCtx}>
<CurrencyContext.Provider value={curCtx}>
{props.children}
</CurrencyContext.Provider>
</LanguageContext.Provider>
);
}
export { ContextRoot };
App.js
import React from 'react';
import { Header } from './Header';
import { Welcome } from './Welcome';
import { Currency } from './Currency';
import { ContextRoot } from './ContextRoot';
const App = (props) => {
return (
<ContextRoot>
<div>
<Header />
<Welcome />
<Currency />
</div>
</ContextRoot>
);
}
export { App };
Header.js
import React, { useContext } from 'react';
import { LanguageContext } from '../context/LanguageContext';
import { CurrencyContext } from '../context/CurrencyContext';
const Header = (props) => {
const { changeLang } = useContext(LanguageContext);
const { changeCur } = useContext(CurrencyContext);
const handleLangClick = (lang) => {
changeLang(lang);
};
const handleCurClick = (cur) => {
changeCur(cur);
};
return (
<div>
<h2>Select your language: <button onClick={e => handleLangClick('english')}>English </button> <button onClick={e => handleLangClick('spanish')}>Spanish</button></h2>
<h2>Select your Currency: <button onClick={e => handleCurClick('$')}>Dollars </button> <button onClick={e => handleCurClick('€')}>Euros</button></h2>
</div>
);
};
export { Header };
Welcome.js
import React, { useContext } from 'react';
import { LanguageContext } from '../context/LanguageContext';
const Welcome = (props) => {
console.log('welcome..');
const { lang } = useContext(LanguageContext);
return (
<div>
<h1>{lang === 'english' ? 'Welcome' : 'Bienvenidos'}</h1>
</div>
);
};
export { Welcome };
Currency.js
import React, { useContext } from 'react';
import { CurrencyContext } from '../context/CurrencyContext';
const Currency = () => {
console.log('currency..');
const { cur } = useContext(CurrencyContext);
return (
<h2>Your chosen currency: {cur}</h2>
)
}
export { Currency };
what you need is useMemo. It's pretty easy to implement, take a look in docs to apply your needs. Hope help you :)
https://reactjs.org/docs/hooks-reference.html#usememo
I wrote here is the code
import React, { FC, Fragment, useEffect } from "react";
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
const HandlerErr: FC<{ error: string }> = ({ error }) => {
useEffect(()=>{
const time = setTimeout(() => {history.push(`/`)}, 2000);
return(()=> clearTimeout(time));
},[error])
return (
<Fragment>
<div>{error}</div>
<div>{"Contact site administrator"}</div>
</Fragment>
);
};
I use the HandlerErr component to redirect. but for some reason it doesn't work history.push (/).I took a video
You need to use history form the react-router-dom
like
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Test extends Component {
render () {
const { history } = this.props
return (
<div>
<Button onClick={() => history.push('./path')}
</div>
)
}
}
export default withRouter(Test)
import React, { FC, useEffect } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface OwnProps {
error: string;
}
type Props = OwnProps & RouteComponentProps<any>;
const HandlerErr: FC<Props> = ({ error, history }) => {
useEffect(() => {
const timeout = setTimeout(() => {
history.push(`/`);
}, 2000);
return () => {
clearTimeout(timeout);
};
}, [error]);
return (
<>
<div>{error}</div>
<div>Contact site administrator</div>
</>
);
};
export default withRouter(HandlerErr);