Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide example using Context API with custom _app.js #4182

Closed
RustyDev opened this issue Apr 20, 2018 · 21 comments · Fixed by #5154
Closed

Provide example using Context API with custom _app.js #4182

RustyDev opened this issue Apr 20, 2018 · 21 comments · Fixed by #5154
Labels
good first issue Easy to fix issues, good for newcomers

Comments

@RustyDev
Copy link

RustyDev commented Apr 20, 2018

It would be great for those of us looking to use React's new Context API and the new custom _app.js in Canary to have an example app to view.

The custom app component persists state between pages and seems like a great way to make use of the Context API at the app container level. Please provide an example showing how to set this up with providers, consumers and multiple pages and how to best deal with server-side rendering and getInitialProps. Basically, how to replace Redux with Context.

@imbhargav5
Copy link
Contributor

imbhargav5 commented Apr 20, 2018

Exactly. I can give this a shot @timneutkens . Although replacing Redux with just context might be hard. I think SSR is really well done with Redux's reducers way.

@RustyDev
Copy link
Author

There are libraries that are reduxish but even any global state example that uses _app.js, SSR and getInitialProps on various pages would be really appreciated. 🙏

@jaydenseric
Copy link
Contributor

This would be blocked by #4194: Context provided in _app.js is undefined when consumed in pages during SSR.

@timneutkens
Copy link
Member

The blocker has been fixed in Next.js 7 canary (next@canary).

@timneutkens timneutkens added help wanted good first issue Easy to fix issues, good for newcomers labels Sep 3, 2018
@Xunnamius
Copy link

@timneutkens Thank you for your hard work! Is there any word on the OP's requested example?

@timneutkens
Copy link
Member

Feel free to create one, should be relatively easy to figure out 👍

@wesbos
Copy link
Contributor

wesbos commented Sep 12, 2018

I have an example if anyone wants to clean it up and turn it into a PR:

NoteProvider.js

import React, { Component } from 'react';

// first we will make a new context
const NoteContext = React.createContext();

// Then create a provider Component
class NoteProvider extends Component {
  state = {
    age: 100,
  };
  render() {
    return (
      <NoteContext.Provider
        value={{
          state: this.state,
          growAYearOlder: () =>
            this.setState({
              age: this.state.age + 1,
            }),
        }}
      >
        {this.props.children}
      </NoteContext.Provider>
    );
  }
}

// then make a consumer which will surface it
const NoteConsumer = NoteContext.Consumer;

export default NoteProvider;
export { NoteConsumer };

_app.js

import App, { Container } from 'next/app';
import NoteProvider from '../components/NoteProvider';

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;
    return (
      <Container>
          <NoteProvider>
            <Component {...pageProps} />
          </NoteProvider>
      </Container>
    );
  }
}

export default MyApp;

Then some page with that component on it:
NoteList.js

import React, { Component } from 'react';
import { NoteConsumer } from './NoteProvider';

class NotesList extends Component {
  render() {
    return (
      <div>
        <NoteConsumer>
          {({ state, growAYearOlder }) => (
            <p>
              hi I'm {state.age}
              <button onClick={growAYearOlder}>Grow</button>
            </p>
          )}
        </NoteConsumer>
      </div>
    );
  }
}
export default NotesList;

@maurodaprotis
Copy link
Contributor

@wesbos i just try your code. But whenever it's server side rendered i get "Cannot read property 'state' of undefined" on NotesList. Works fine on client side render.

I'm on next 6.1.2

@wesbos
Copy link
Contributor

wesbos commented Sep 12, 2018 via email

@NangoliJude
Copy link

NangoliJude commented Sep 18, 2018

Here is another example of Next Js and React Context I also added styled components https://github.com/NangoliJude/React-Context-Api-with-Next-Js

@msreekm
Copy link
Contributor

msreekm commented Sep 27, 2018

question, how do I access context api in _app.js (getInitialProps) ., this is my use case, get refresh token on page load (if cookie is not present), store the returned session info in context ! this should happen if I hit any of the pages.

@maurodaprotis
Copy link
Contributor

I run into the same issue. You can pass a prop to the context provider but it will not be available until render of the app component.

@msreekm
Copy link
Contributor

msreekm commented Sep 27, 2018

@maurodaprotis ,any solutions?

@lyubchev
Copy link

Cannot find context namespace with typescript

@quantafire
Copy link

Does anyone have an example that doesn't use a Consumer, but rather:
ThemedButton.contextType = ThemeContext;
as a static variable?

Having trouble getting it working without a Consumer!

@timneutkens
Copy link
Member

That'd only work on newer versions of React (16.6), make sure you're on that version

@zachweinberg
Copy link

Just throwing it out there, but the package easy-peasy makes it insanely easy to do this with Next now. It's a wrapper around React Hooks.

@eagor
Copy link

eagor commented Feb 17, 2020

I woudn't use easy-peasy already because of the name 😄

@bhaveshpatel200
Copy link

Hello,
I have a context provider file below and _app.js file.
i want access 'AppContext' in '_app.js' file.
can any one help me, how can i access the 'AppContext' in '_app.js' file.

Context file

`import React, { createContext, useState } from 'react';

export const AppContext = createContext();
let timeout = null;

const baseURL = "api/v1/";
const headers = { 'Accept': 'application/json', "Content-type": "application/json" };

const AppContextProvider = (props) => {
const [account, setAccount] = useState();
const [alert, setAlert] = useState();

const AlertType = { success: "success", error: "error", info: "info", warning: "warning" }
const updateAccount = (objAccount) => {
    setAccount(objAccount);
}

const updateAlert = (objAlert) => {
    if (objAlert) {
        if (objAlert.type == AlertType.success) { objAlert = { message: objAlert.message, type: objAlert.type, cssClass: "alert-teal" }; }
        else if (objAlert.type == AlertType.error) { objAlert = { message: objAlert.message, type: objAlert.type, cssClass: "alert-danger" }; }
        else if (objAlert.type == AlertType.warning) { objAlert = { message: objAlert.message, type: objAlert.type, cssClass: "alert-warning" }; }
        else if (objAlert.type == AlertType.info) { objAlert = { message: objAlert.message, type: objAlert.type, cssClass: "alert-info" }; }
    }
    setAlert(objAlert);

    clearTimeout(timeout);
    timeout = setTimeout(() => { setAlert(); }, 8000);
}


const ExecuteAPI_Get = async (action) => {
    let url = baseURL + action;
    let response = await fetch(url, {
        method: 'GET',
        headers: headers
    }).catch(err => { console.log(err); });
    return await response.json();
}
const ExecuteAPI_Post = async (action, params) => {
    let cookie = getCookie('Bearer');
    if (cookie) { headers['Authorization'] = 'Bearer ' + cookie; }
    let url = baseURL + action;
    let response = await fetch(url, {
        method: 'POST',
        headers: headers,
        body: JSON.stringify(params),
    }).catch(err => { console.log(err); });
    return await response.json();
}

//Cookies
const setCookie = (cname, cvalue, date) => {
    var expires;
    if (typeof date === "number") {
        let d = new Date(); d.setTime(d.getTime() + (parseInt(date) * 24 * 60 * 60 * 1000));
        expires = "expires=" + d.toUTCString();
    }
    else { expires = "expires=" + date.toString(); }
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
const getCookie = (cname) => {
    var name = cname + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}



return (
    <AppContext.Provider value={{ account, updateAccount, alert, updateAlert, ExecuteAPI_Get, ExecuteAPI_Post, setCookie, getCookie }}>
        {props.children}
    </AppContext.Provider>
)

}

export default AppContext
export { AppContextProvider }

`

_app.js file

//import '../styles/bootstrap.min.css';
//import '../styles/global.css';
import '../public/assets/css/font-awesome.min.css';
import '../public/assets/css/common.css';
import '../public/assets/css/style.css';

import Router from 'next/router';
import { AppContext, AppContextProvider } from '../components/contextprovider';
import App from "next/app";
import { PageTransition } from 'next-page-transitions';
import Layout from '../components/layout/layout';

//context not found
export default class MyApp extends App {
static contextType = AppContext;
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) { pageProps = await Component.getInitialProps(ctx) }
return { pageProps }
}

constructor(props, context) {
    super(props, context);
    Router.events.off('routeChangeStart', () => { }); Router.events.off('routeChangeComplete', () => { }); Router.events.off('routeChangeError', () => { });
    Router.events.on('routeChangeStart', (url) => { }); Router.events.on('routeChangeComplete', (url) => { }); Router.events.on('routeChangeError', (url) => { });
}
componentDidMount() { this.CheckLogin(); }

CheckLogin = () => {
    let cookie = this.getCookie("Bearer");
    if (cookie) {
        //this.context.ExecuteAPI_Post('auth/me', {}).then((res) => {
        //    console.log(res);
        //    if (res && res.data) { this.context.updateAccount(res.data); }
        //});

        if ((Router.pathname == '/login' || Router.pathname == '/signup')) { Router.push('/'); }
    }
    else {
        if (Router.pathname != '/signup') {
            this.setCookie("Bearer", "", 0);
            Router.push('/login');
        }

    }
}
//Temporory after get context in _app.js then remove this code and get from context
setCookie = (cname, cvalue, date) => {
    var expires;
    if (typeof date === "number") {
        let d = new Date(); d.setTime(d.getTime() + (parseInt(date) * 24 * 60 * 60 * 1000));
        expires = "expires=" + d.toUTCString();
    }
    else { expires = "expires=" + date.toString(); }
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
getCookie = (cname) => {
    var name = cname + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

render() {
    const { Component, pageProps } = this.props;
    return (
        <AppContextProvider>
            <Layout>
                <PageTransition timeout={300} classNames="page-transition-main page-transition">
                    <Component {...pageProps} />
                </PageTransition>
            </Layout>

            <style jsx global>{`
                .page-transition-main{width:100%}
                .page-transition-enter {opacity: 0;}
                .page-transition-enter-active {opacity: 1;}
                .page-transition-exit {opacity: 1;}
                .page-transition-exit-active {opacity: 0;transition: opacity 300ms;}
          `}</style>
        </AppContextProvider>
    );
}

}

@abhijithvijayan
Copy link

abhijithvijayan commented Jul 15, 2020

@wesbos Any way you can provide the hooks version?

I was hoping to connect to a socket server and respond to its callbacks through the page root so that page transitions doesn't loose the connection to the server.

I have problems trying to get the useContext() hook to dispatch the useReducer() actions when the callbacks are called. Since the useContext() hook returns undefined when not used from the parent tree, I cant use it to dispatch it from _app.js useEffect() hook when the provider itself is wrapping the component

_app.js

...
useEffect(() => {
   // initialize socket connection
   ...
   socket.on('something', () => {
      // dispatch using `useContext()`
   })
}, []);

// here wraps the component with context provider

Update I tried this approach of calling useEffect() inside the context provider component, but it still disconnects and reconnects to the socket server on page transition.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
good first issue Easy to fix issues, good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.