I'm using Next.js with context API and styled components and I can't seem to get getStaticProps working.
I have read other posts and often they talk about the custom _app which I do have but I never ran into the issue before using context API. I have also tried the getInitialProps function and to no avail.
I should also note that even after not including the context wrapper I don't get a response from a function so I'm not at all sure of where to look.
Here is my code. Can you see what's going on?
import React from 'react';
import fetch from 'node-fetch';
export default function Header(props) {
console.log(props.hi);
return <div>Hey dis header</div>;
}
export async function getStaticProps(context) {
return {
props: {
hi: 'hello',
},
};
}
I have tried logging from the function but nothing is logging so I would imagine the problem is that the function isn't running for whatever reason.
Heres my custom _app file
import { GlobalContextWrapper } from 'context';
import Header from 'components/header/Header';
import App from 'next/app';
function MyApp({ Component, pageProps }) {
return (
<GlobalContextWrapper>
<Header />
<Component {...pageProps} />
<p>footer</p>
</GlobalContextWrapper>
);
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps };
};
export default MyApp;
Here is my context file
import { useReducer } from 'react';
import initialState from './intialState';
import reducer from './reducer';
import GlobalStyle from '../GlobalStyle';
import theme from '../theme';
import { ThemeProvider } from 'styled-components';
export const GlobalContext = React.createContext();
export function GlobalContextWrapper({ children }) {
const [globalState, dispatch] = useReducer(reducer, initialState);
return (
<GlobalContext.Provider value={{ globalState, dispatch }}>
<GlobalStyle />
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</GlobalContext.Provider>
);
}
The issue was that i was not exporting this function from a page but instead a component and a custom app file.
Does anyone know a way i can get around this? The problem is that i have a header that gets data from a response and i want this header to be shown on every page without having to manually add it to each page along with repeating the getStaticProps function
A solution based on your code is just getting data in your _app.js - getInitialProps and pass to the Header
function MyApp({ Component, pageProps }) {
return (
<GlobalContextWrapper>
<Header data={pageProps.header}/>
<Component {...pageProps} />
<p>footer</p>
</GlobalContextWrapper>
);
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
const headerData = ....
return { ...appProps, header: headerData };
};
Related
I am trying to import grapesjs in a nextjs project and I get the error TypeError: Cannot read properties of null (reading 'querySelector')
This seems to be that grapesjs wants to target the "#gjs" container referenced through it's id in order to load the editor inside, and it cannot find the corresponding element as the DOM is not rendered yet.
This is the code in my Editor.js component
import React, { useEffect, useState } from "react";
import grapesjs from "grapesjs";
const Editor = () => {
const [editor, setEditor] = useState(null);
useEffect(() => {
const editor = grapesjs.init({
container: "#gjs",
});
setEditor(editor);
}, []);
return (
<>
<div id="gjs"></div>
</>
);
};
export default Editor;
This is how I try to render the Editor component in the corresponding page for "/editor" route
import { getSession } from "next-auth/react";
import "../i18n/config/config";
import "grapesjs/dist/css/grapes.min.css";
import dynamic from "next/dynamic";
import Editor from "../features/Editor/components/Editor";
// const EditorComponent = dynamic(
// () => import("../features/Editor/components/Editor"),
// {
// ssr: false,
// }
// );
export default function Home() {
return <Editor />;
}
export async function getServerSideProps(context) {
const session = await getSession(context);
return {
props: {
session,
},
};
}
As you can see from the commented section, I have tried to dynamically import the editor component as I have seen this as a fix for alot of issues where an element could not be found because the DOM was not yet loaded, but it does not seem to work for me.
Edit: Adding <script src="//unpkg.com/grapesjs"></script> before the component to be rendered either in Editor.js component or in editor.js page while removing the grapejs import statement from Editor.js component import grapesjs from "grapesjs" allows the application to run but I still get the error in the console.
import { getSession } from "next-auth/react";
import "../i18n/config/config";
import "grapesjs/dist/css/grapes.min.css";
import dynamic from "next/dynamic";
import Editor from "../features/Editor/components/Editor";
// const EditorComponent = dynamic(
// () => import("../features/Editor/components/Editor"),
// {
// ssr: false,
// }
// );
export default function Home() {
return (
<>
<script src="//unpkg.com/grapesjs"></script>
<Editor />
</>
);
}
export async function getServerSideProps(context) {
const session = await getSession(context);
return {
props: {
session,
},
};
}
Edit: Screenshot of the produced error
Because it is wrong type in useState. U can try this.
const Editor = () => {
const [editor, setEditor] = useState<Record<string, any> | null>(null);
useEffect(() => {
const editor = grapesjs.init({
container: "#gjs",
});
setEditor(editor);
}, []);
return (
<>
<div id="gjs"></div>
</>
);
};
export default Editor;
Search the library sources..
I need to access the context API in my _app.js file in order to set global state triggered by the router events. The reason for this is to set a loading state which can be accessed by individual components throughout the app. The problem is is the context is provided from the _app.js file, so I don't have the context's context as it were.
context.js
import React, { createContext, useState } from "react";
export const Context = createContext();
const ContextProvider = (props) => {
const [isLoading, setIsLoading] = useState(false);
return (
<Context.Provider
value={{
isLoading,
setIsLoading,
}}
>
{props.children}
</Context.Provider>
);
};
export default ContextProvider;
_app.js
import React, { useContext } from "react";
import App from "next/app";
import Head from "next/head";
import Aux from "../hoc/Aux";
import ContextProvider, { Context } from "../context/context";
import { withRouter } from "next/router";
class MyApp extends App {
static contextType = Context;
componentDidMount() {
this.props.router.events.on("routeChangeStart", () => {
this.context.isLoading(true);
});
this.props.router.events.on("routeChangeComplete", () => {
this.context.isLoading(false);
});
this.props.router.events.on("routeChangeError", () => {
this.context.isLoading(false);
});
}
render() {
const { Component, pageProps } = this.props;
return (
<Aux>
<Head>
<title>My App</title>
</Head>
<ContextProvider>
<Component {...pageProps} />
</ContextProvider>
</Aux>
);
}
}
export default withRouter(MyApp);
Clearly this wouldn't work since _app.js is not wrapped in the context provider. I've tried moving the router event listeners further down the component tree, but then I don't get the loading state from my home page to my dynamically created pages that way.
Is there any workaround that lets me consume context in _app.js? I can't think of any other way I can access loading state globally to conditionally load specific components.
It's not clear to me why you need the context provider to be a parent of _app.js (or a separate component at all). Wouldn't the following work?
class MyApp extends App {
state = {
isLoading: false,
};
componentDidMount() {
this.props.router.events.on("routeChangeStart", () => {
this.setIsLoading(true);
});
this.props.router.events.on("routeChangeComplete", () => {
this.setIsLoading(false);
});
this.props.router.events.on("routeChangeError", () => {
this.setIsLoading(false);
});
}
render() {
const { Component, pageProps } = this.props;
return (
<Aux>
<Head>
<title>My App</title>
</Head>
<Context.Provider
value={{
isLoading: this.state.isLoading,
setIsLoading: this.setIsLoading,
}}>
<Component {...pageProps} />
</Context.Provider>
</Aux>
);
}
setIsLoading = (isLoading) => {
this.setState({ isLoading });
}
}
export default withRouter(MyApp);
Alternatively (if there's something I'm really not understanding about your use case), you could create a HoC:
function withContext(Component) {
return (props) => (
<ContextProvider>
<Component {...props} />
</ContextProvider>
);
}
class MyApp extends App {
...
}
export default withContext(withRouter(MyApp));
You can show a loading indicator using nprogress. For example:
import NProgress from "nprogress";
import Router from "next/router";
Router.onRouteChangeStart = () => NProgress.start();
Router.onRouteChangeComplete = () => NProgress.done();
Router.onRouteChangeError = () => NProgress.done();
Source
I'm working on Server Side Rendering react app with React Js and Next Js as my framework, and I'm trying to fetch initial props with getServerSideProps method by referring to the document (next js doc for getServerSideProps) too.
But I'm always getting empty props object as {} on every request. For now, I'm just trying to pass a dummy text in props.
How can I get my props on the initial load?
Please refer my code below
import React from "react";
import { Provider } from "react-redux";
import ConfigureStore from "../Store";
const Home = props => {
const store = ConfigureStore();
console.log("props", props);
return (
<Provider store={store}>
<div>My content</div>
</Provider>
);
};
// This gets called on every request
export async function getServerSideProps() {
const data = "Hello World";
return { props: data };
}
export default Home;
The problem with getServerSideProps is that it can only be exported from a page. You can’t export it from non-page files. Same goes with getStaticProps.
So you can not use it in partial components.
Which means you can only use it directly on files in /page directory.
import React from "react";
import { Provider } from "react-redux";
import ConfigureStore from "../Store";
const Home =({ data }) => {
console.log("data", data);
return (
<div>My content</div>
);
};
// This gets called on every request
export async function getServerSideProps() {
const data = "Hello World";
return { props: {data :"Hello World"} };
}
export default Home;
I am trying to optimise the initial page bundle size for an application. I am trying to defer loading the firebase bundle until I load a component that uses redux to make database calls.
Following is the actions file:
import { DB } from '../../firebase/initialize';
export const addText = (text, callback) => async dispatch => {
dispatch({
type: 'ADD_TEXT',
status: 'started',
});
DB.collection('texts').then(() => {
// Do something
});
};
This is loading firebase which is loading approx 100KB of code. I wanted to do load this code only after the site has completed loading.
So, I am lazy loading the component TextList that has dependency to redux action which uses firebase to get data. I was expecting this would make my actions and firebase be part of a different bundle created for TextList component and its dependency. But this is not the case.
// import react and others
import configureStore from './redux/stores/store';
import Home from './components/molecules/home/home';
ReactDOM.render(
<Provider store={configureStore()}>
<Suspense fallback={<div>Loading...</div>}>
<Home />
</Suspense>
</Provider>,
document.getElementById('root')
);
import React, { Component, lazy } from 'react';
const TextList = lazy(() => import('../../compounds/TextList/text-list'));
class Home extends Component {
render() {
return (
<div className="home-page">
<TextList />
</div>
);
}
}
export default Home;
And when Home loads, it loads redux actions at last:
import React, { Component, Suspense, lazy } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../../redux/actions/actions';
class TextList extends Component {
componentDidMount() {
this.props.fetchSnippet();
}
render() {
return // template
}
}
const mapStateToProps = state => ({
...state,
});
export default connect(
mapStateToProps,
actions
)(TextList);
What approach should I follow to lazy load firebase and component using the same.
You can use a dynamic import for the firebase module in your actions file :shrug:
const getDB = async () => await import('../../firebase/initialize');
export const addText = (text, callback) => async dispatch => {
dispatch({
type: 'ADD_TEXT',
status: 'started',
});
const DB = await getDB();
DB.collection('texts').then(() => {
// Do something
});
};
I add header component to layout but i don't want to send props from layout to header for every page I want use getInitialProps
layout.js
import Header from './header'
export default ({title}) => (
<div>
<Head>
<title>{ title }</title>
<meta charSet='utf-8' />
</Head>
<Header />
{children}
</div>
)
header.js
export default class Header extends Component {
static async getInitialProps () {
const headerResponse = await fetch(someapi)
return headerResponse;
}
render() {
console.log({props: this.props})
return (
<div></div>
);
}
}
console: props: {}
App.js
import Layout from './layout'
import Page from './page'
import axios from 'axios'
const app = (props) => (
<Layout >
<Page {...props}/>
</Layout>
)
app.getInitialProps = async function(){
try {
const response = await axios.get(someUrl)
return response.data;
} catch(e) {
throw new Error(e);
}
}
export default app
I want to use get initial props in Header component but it not firing
EDIT: 2021 way of doing this :
// with typescript remove type if you need a pure javascript solution
// _app.tsx or _app.jsx
import type { AppProps } from 'next/app';
import Layout from '../components/Layout';
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout {...pageProps}>
<Component {...pageProps} />
</Layout>
);
}
// In order to pass props from your component you may need either `getStaticProps` or `getServerSideProps`.
// You should definitely not do that inside your `_app.tsx` to avoid rendering your whole app in SSR;
// Example for SSR
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
Next.js uses the App component to initialize pages. You can override it and control the page initialization.
What you could do is, put your logic inside the _app override and pass it to your children components, example with a Layout component.
Create a page/_app.js
import React from 'react'
import App, { Container } from 'next/app'
import Layout from '../components/Layout'
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
/* your own logic */
return { pageProps }
}
render () {
const { Component, pageProps } = this.props
return (
<Container>
<Layout {...pageProps}>
<Component {...pageProps} />
</Layout>
</Container>
)
}
}
There is a good example from zeit at github