NextJS: How to add screen loading for production build? - reactjs

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();
}

Related

Invalid element type when using Context to pass down Auth details

I'm currently attempting to build an 'AuthContext' so I can use it in various screens and pass the data down.
I thought I'd built it right.. But when I try to call one of the functions in my Provider, it's throwing a component exception, stating 'element type is invalid: expected a string or a class/function but got undefined'.
Here is the context file:
import React, { useState, useContext } from 'react';
import { navigate } from '../navigationRef';
import { Magic } from '#magic-sdk/react-native';
const m = new Magic('api key');
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState([]);
const userSignedIn = async () => {
// Call Magic logged in
const loggedIn = await m.user.isLoggedIn();
// If user logged in, save details to user, and redirect to dashboard
if (loggedIn === true) {
const { issuer, email } = await m.user.getMetaData();
setUser([issuer, email])
navigate('authorisedFlow')
// If user not logged in, redirect to login flow
} else {
navigate('loginFlow')
}
};
const signIn = () => {
};
const signUp = () => {
};
const logOut = () => {
};
return (
<AuthContext.Provider value={{ user, userSignedIn, signIn, signUp, logOut }}>
{ children }
</AuthContext.Provider>
)
}
And here is the component which is attempting to use the context:
import React, { useContext, useEffect } from 'react';
import { View, StyleSheet, ActivityIndicator } from 'react-native';
import AuthContext from '../context/AuthContext';
const LoadingScreen = ({ navigation }) => {
const { userSignedIn } = useContext(AuthContext)
useEffect(() => {
userSignedIn()
}, [])
return (
<View style={styles.mainView}>
<ActivityIndicator style={styles.indicator} />
</View>
)
}
And finally, here is my app.js file (cut most of it out due to length, but wanted to show Provider):
import { Provider as AuthProvider } from './src/context/AuthContext';
const App = createAppContainer(switchNavigator)
export default () => {
return (
<AuthProvider>
<App />
</AuthProvider>
)
};
Can anyone see what's going wrong here?
You exported your AuthContext as a named-export ... but you're importing a default-export
import AuthContext from '../context/AuthContext'; // <--- Here
const LoadingScreen = ({ navigation }) => {};
Instead...
import { AuthContext} from '../context/AuthContext';
Same goes for this one as well...
import { Provider as AuthProvider } from './src/context/AuthContext';
Which should be
import { AuthContext: { Provider as AuthProvider } } from './src/context/AuthContext';
OR
import { AuthContext } from './src/context/AuthContext';
return (
<AuthContext.Provider>
<App />
</AuthContext.Provider>
)

Could not find "store" in the context of "Connect(withRedux(MyApp))"

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

Next JS code inside getInitialProps not executes after page reload

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.

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 .

Having issues with dynamic routes on Next 9

GraphQL queries in my components are not running on dynamic routes when I try to access the query string with router.query.xxx.
I have the following file
// ./pages/section/[slug].js
import { useRouter } from 'next/router';
import AppLayout from '../../components/styles/_AppLayout';
const Section = () => {
const router = useRouter();
return <AppLayout>Hi</AppLayout>;
};
export default Section;
The page displays fine, but as soon as I add {router.query.slug} and refresh the page, it gives me a TypeError because the GraphQL queries do not run. As you can see in the image below, me.firstName is undefined because the GraphQL query did not run
This is the code in _AppLayout.js
import styled from 'styled-components';
import Navigation from '../Navigation';
const Wrapper = styled.div`...`;
const AppLayout = props => {
return (
<Wrapper>
<Navigation />
<main>{props.children}</main>
</Wrapper>
);
};
export default AppLayout;
Any ideas why this might be happening and how to fix it?
Thanks
I was able to solve my issue two ways
Using withRouter
import { withRouter } from 'next/router';
import TestComponent from '../../components/TestComponent';
import AppLayout from '../../components/styles/_AppLayout';
const Section = props => {
return <AppLayout>Hi {props.query.slug}</AppLayout>;
};
export default withRouter(Section);
and passing the query parameter as props via getInitialProps
const Section = ({slug}) => {
return <AppLayout>Hi {slug}</AppLayout>;
};
Section.getInitialProps = async ({ query }) => {
const { slug } = query;
return { slug };
};
export default Section;
The following method worked for me, I am using React Hooks with Context and I need to also use the nextJS route with it, so following configuration can be followed.
Note: If you are using GraphQL then that can be also wrapped around the final JSX in _app.js
_app.js:
import { withRouter } from "next/router";
BuilderProvider is here Context Provider
const InjectRouterContext = withRouter(({ router, children }) => {
return <BuilderProvider value={router}>{children}</BuilderProvider>;
});
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<InjectRouterContext>
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
</InjectRouterContext>
);
}
}
Now in the Page, here it is somepage.js:
import { useRouter } from "next/router";
const somepage = () => {
const router = useRouter();
const { id } = router.query;
return (//JSX Here);
}
somepage.getInitialProps = async ({ query }) => {
const { slug } = query;
return { slug };
};
export default somepage;

Resources