set cookie before running fetch in getInitialProps - javascript

I have a Next.js app, I'm using getInitialProps in my _app.js in order to be able to have persistent header and footer. However, I'm also needing to set data in a Context, and I need to be able to fetch the data based off of a cookie value. I've got the basics working just fine, however my _app sets the cookie on the first load, and then when I refresh the page it pulls in the appropriate data. I'm wondering if there's a way to be able to set the cookie first before fetching the data, ensuring that, if there's a cookie present, it will always pull in that data on the first load? Here is my _app.js, and, while I'm still working on the dynamic cookie value in my cookies.set method, I'm able to fetch the right data from my Prismic repo by hard-coding sacramento-ca for now, as you'll see. All I'm really needing is the logic to ensure that the cookie sets, and then the data fetches.
_app.js
import React from 'react';
import { AppLayout } from 'components/app-layout/AppLayout';
import { Footer } from 'components/footer/Footer';
import { Header } from 'components/header/Header';
import { LocationContext } from 'contexts/Contexts';
import Cookies from 'cookies';
import { Client } from 'lib/prismic';
import NextApp, { AppProps } from 'next/app';
import 'styles/base.scss';
import { AppProvider } from 'providers/app-provider/AppProvider';
interface WithNavProps extends AppProps {
navigation: any;
location: string;
dealer?: any;
cookie: string;
}
const App = ({ Component, pageProps, navigation, dealer }: WithNavProps) => {
const { Provider: LocationProvider } = LocationContext;
const locationData = dealer ? dealer : null;
return (
<LocationProvider value={{ locationData }}>
<AppProvider>
<AppLayout>
<Header navigation={navigation} location={dealer} />
<Component {...pageProps} />
<Footer navigation={navigation} />
</AppLayout>
</AppProvider>
</LocationProvider>
);
};
export default App;
App.getInitialProps = async (appContext: any) => {
const appProps = await NextApp.getInitialProps(appContext);
const cookies = new Cookies(appContext.ctx.req, appContext.ctx.res);
try {
cookies.set('dealerLocation', 'sacramento-ca', {
httpOnly: true,
});
const { data: navigation } = await Client.getByUID('navigation', 'main-navigation', {
lang: 'en-us',
});
const results = await Client.getByUID('dealer', cookies.get('dealerLocation'), {
lang: 'en-us',
});
return {
...appProps,
navigation,
dealer: results,
};
} catch {
const { data: navigation } = await Client.getByUID('navigation', 'main-navigation', {
lang: 'en-us',
});
return {
...appProps,
navigation,
};
}
};

Related

Can I use getInitialProps in _app.js and in pages?

I'm working on my first serious NextJS app. I have it set up to pull in JSON data for the left nav, rather than hardcoding them in the app somewhere. This way I don't have to rebuild every time there's a minor change to the site's navigation.
Since the navigation needs to be available on every page, I added getInitialProps to the _app.js file, which grabs the left nav and passes it to the left nav component. But now as I'm moving on to build the homepage, I see that the getInitialProps there does not run. It seems that the getInitialProps in _app.js takes precendence.
Is there a way to have both? Or some other workaround that accomplishes the goal (or just a better way to do this in general)?
Note that I'm using getInitialProps for two reasons:
getStaticProps is out because I don't plan to build the entire site at build time
getServerSideProps is usually out because I don't like that it ends up doing two http requests: first a request goes to the NextJS server, then the server sends a request to my API (which happens to live on a different server). If I'm just getting basic stuff like the navigation, there's no need for getServerSideProps to run on the NextJS server, I'd rather skip the middle man
Here's some some simplified code:
_app.js:
import { Provider } from "react-redux";
import axios from "axios";
import store from "../store";
import Header from "../components/Header";
import LeftNav from "../components/LeftNav";
function MyApp(props) {
const { Component, pageProps } = props;
return (
<Provider store={store}>
<Header />
<LeftNav leftnav={props.leftnav} />
<Component { ...pageProps } />
</Provider>
)
}
MyApp.getInitialProps = async (context) => {
let config = await import("../config/config");
let response = await axios.get(`${config.default.apiEndpoint}&cAction=getLeftNav`);
if (response) {
return {
leftnav: response.data.leftNav
};
} else {
return {
leftnav: null
};
}
};
export default MyApp;
Home.js:
import axios from "axios";
const Home = (props) => {
console.log("Home props", props);
return (
<div>home</div>
);
};
Home.getInitialProps = async(context) => {
// this only runs if the getInitialProps in _app.js is removed :(
let config = await import("../config/config");
let response = await axios.get( `${config.default.apiEndpoint}&cAction=getHome` );
if ( response ) {
return {
home: response.data.home
};
} else {
return {
home: null
}
}
};
export default Home;
You have to call App.getInitialProps(context) in your _app to call the current page's getInitialProps. You can then merge the page's props with the remaining props from _app.
import App from 'next/app'
// Remaining code...
MyApp.getInitialProps = async (context) => {
const pageProps = await App.getInitialProps(context); // Retrieves page's `getInitialProps`
let config = await import("../config/config");
let response = await axios.get(`${config.default.apiEndpoint}&cAction=getLeftNav`);
return {
...pageProps,
leftnav: response?.data?.leftNav ?? null
};
};
From the custom _app documentation:
When you add getInitialProps in your custom app, you must import App from "next/app", call App.getInitialProps(appContext) inside
getInitialProps and merge the returned object into the return value.

Environment Variables are not showing up in my Context component, on Nextjs. Would I need to configure Nextjs? Or set up Context to use the variables?

Environment Variables are working on every component inside /pages but not in my Context component, in Nextjs. I'm wondering would Nextjs need some configuration?
(Note: Shopcontext.tsx is using a class component, that I got from a tutorial and I'm not familiar enough with context api to change it to a functional component. I tried.)
/* Below Shopcontext.tsx */
import React, { Component } from "react";
import Client from "shopify-buy";
const ShopContext = React.createContext({});
const client = Client.buildClient({
domain: "benson-bracelets.myshopify.com",
storefrontAccessToken: process.env.SHOPIFY_ACCESS_TOKEN as string,
});
interface State {
product: {};
products: Array<any>;
checkout: any;
}
export class ShopProvider extends Component {
state: State = {
product: {},
products: [],
checkout: {},
};
componentDidMount() {
if (localStorage.checkout_id) {
this.fetchCheckout(localStorage.checkout_id);
} else {
this.createCheckout();
}
}
/**
* Local storage will hold the checkoutid.
* Shopify will handle the check eachtime a checkout is started.
* #memberOf ShopProvider
*/
createCheckout = async () => {
const checkout = await client.checkout.create();
localStorage.setItem("checkout_id", checkout.id as any);
this.setState({ checkout: checkout });
};
fetchCheckout = (checkoutId: any) => {
client.checkout
.fetch(checkoutId)
.then((checkout) => {
this.setState({ checkout: checkout });
})
.catch((err) => {
console.log("Error Message, in ShopContext fetchCheckout: ", err);
});
};
fetchAllProducts = async () => {
await client.product.fetchAll().then((products) => {
this.setState({ products });
});
};
render() {
return (
<ShopContext.Provider
value={{
...this.state,
fetchAllProducts: this.fetchAllProducts,
fetchCheckout: this.fetchCheckout,
}}
>
{this.props.children}
</ShopContext.Provider>
);
}
}
const ShopConsumer = ShopContext.Consumer;
export { ShopConsumer, ShopContext };
export default ShopProvider;
/* .env file below */
SHOPIFY_DOMAIN=MY_DOMAIN_WOULD_BE_HERE
SHOPIFY_ACCESS_TOKEN=MY_API_WOULD_BE_HERE
/* _app.tsx below */
import React from "react";
import { AppProps } from "next/app";
import { ThemeProvider } from "#material-ui/core/styles";
import Theme from "../src/ui/Theme";
import { AnimatePresence } from "framer-motion";
import ShopContext from "../src/context/ShopContext";
import { wrapper } from "../src/store/store";
function MyApp(props: AppProps) {
const { Component, pageProps } = props;
return (
<React.Fragment>
<ThemeProvider theme={Theme}>
<ShopContext>
<AnimatePresence exitBeforeEnter>
<Component
{...pageProps}
{...props}
/>
</AnimatePresence>
</ShopContext>
</ThemeProvider>
</React.Fragment>
);
}
export default wrapper.withRedux(MyApp);
I would like to add on to #Mohammad Shaban's answer.
Ever since NextJS 9.4 there is support for loading environment variables through .env.local file .
By default all environment variables loaded through .env.local are only available in the Node.js environment, meaning they won't be exposed to the browser.
In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. For example:
NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
So don't forget to prefix the env variables with NEXT_PUBLIC_ in case you are using them in browser.
For more information you can check this link
You should define your environment variable inside the next.config.js at the route of your project then you will get the env variable in every component
module.exports = {
env: {
customKey: 'my-value',
},
}
it is clearly mentioned in the nextjs document that Trying to destructure process.env variables won't work due to the nature of webpack
For more detail please visit the below link
https://nextjs.org/docs/api-reference/next.config.js/environment-variables

How to integrate Firebaseui auth with Redux

I've got a working auth configuration set up using firebaseui. I have a private landing page that I'd like to redirect the user to, but I'm not sure how to pass the credentialed response into my redux store.
I basically want to call the handleClickLogin method (currently hooked to a dummy button) of my Home component from my signInSuccess callback. In other words I'm trying to dispatch(login()); when I get a successfull signin, which in turn adds the flag to my redux store which I can then use to gate my private landing page. Since firebase.js is not in the component tree, I don't have access to dispatch here, so how do I get the response hooked in to my store?
firebase.js
const uiConfig = ({
// signInSuccessUrl: '/',
signInOptions: [
firebase.auth.EmailAuthProvider.PROVIDER_ID,
],
callbacks: {
signInSuccess: (resp) => <<<???>>>,
},
});
firebase.initializeApp(config);
const ui = new firebaseui.auth.AuthUI(firebase.auth());
export const startFirebaseUI = elementId => {
ui.start(elementId, uiConfig);
};
Home.jsx (stripped down)
export class Home extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
componentDidMount = () => {
startFirebaseUI('#firebaseui-auth-container');
}
handleClickLogin = () => {
const { dispatch } = this.props;
dispatch(login());
};
render() {
const { user } = this.props;
return (
<Background>
<HomeContainer>
<Button
onClick={this.handleClickLogin}
>
<Text ml={2}>Start</Text>
</Button>
<div id="firebaseui-auth-container" />
</HomeContainer>
</Background>
);
}
}
function mapStateToProps(state) {
return { user: state.user };
}
export default connect(mapStateToProps)(Home);
Somehow typing the question helped me figured it out. Just needed to import the store and the appropriate action, then dispatch it directly.
import { store } from 'store/index';
import { login } from 'actions/index';
callbacks: {
signInSuccess: (resp) => store.dispatch(login(resp)),
}

Server Side Rendering with react-apollo

Best wishes, while I was using the local yoga server, and local docker container as a database, things worked very smoothly because data was loaded in split second, and thus... if someone was signed in, the name of the signed in person immediately appeared on client and server side as well.
Now that I deployed frontend, backend, and database on remote servers, it takes time to load the data. Due to this, The Sign In button stays for 3-4 seconds even if we were already signed in. And late the frontend realizes, that we were signed in and then shows our name.
This happens because we render the data only after we get the data. But when data comes late, the server-side code becomes outdated. Late updating client makes web app feel very lagging.
I am using Next.js
withData.js 👇
import withApollo from 'next-with-apollo'
import ApolloClient from 'apollo-boost'
function createClient({ headers }) {
return new ApolloClient({
uri: `${process.env.ENDPOINT}/graphql`,
request: operation => {
operation.setContext({
fetchOptions: {
credentials: 'include',
},
headers
})
}
})
}
export default withApollo(createClient);
User.js 👇
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
import PropTypes from 'prop-types'
import { client } from '../lib/withData'
export const CURRENT_USER_QUERY = gql`
query {
me {
id
name
fname
lname
email
phone
bio
previledge
gender
username
birthday
profilePicture
signUpMethod
}
}
`
const User = props => (
<Query {...props} query={CURRENT_USER_QUERY}>
{payload => {
return props.children(payload)
}}
</Query>
)
export default User
SignInButton.js 👇
<User>
{({data: {me}}) => (
{ me ? <ProfileButton me={me} /> : <li style={{backgroundColor: '#ffffff', color: '#000000', borderRadius: '5px', padding: '5px 10px', zoom: '80%'}}><a href={`${meta.domain}/signin?intent=${this.props.router.asPath}`} style={{color: '#000000'}}>⚡️🚦 {this.signInText}</a></li> }
)}
</User>
pages/_app.js 👇
import App, { Container } from 'next/app'
import { ApolloProvider } from 'react-apollo'
import withData from '../src/lib/withData'
import Page from '../src/components/Page'
class Wrapper extends App {
static getInitialProps({Component, ctx}){
let pageProps = {}
if(Component.getInitialProps){
pageProps = Component.getInitialProps(ctx)
}
// This exposes query to the user
pageProps.query = ctx.query
return { pageProps }
}
render() {
const { Component, apollo, pageProps } = this.props
return (
<Container>
<ApolloProvider client={apollo}>
<Page>
<div className="super_container"><Component {...pageProps} /></div>
</Page>
</ApolloProvider>
</Container>
)
}
}
export default withData(Wrapper)
How do I render the data from react-apollo on the server side?
Found these resources but difficult to implement with the stack I use.
https://bessey.dev/blog/2019/01/02/apollo-graphql-hypernova/
https://github.com/i18next/react-i18next/issues/593
https://shaleenjain.com/blog/nextjs-apollo-prefetch/
If you want SSR by query so you can populate head and other stuff directly in serverSide, you need to make the query directly in
You need to create the query inside the GetInitialProps function like this :
Page.getInitialProps = async ({
apolloClient, query, children, router, href
}) => {
const { data, error, loading } = await apolloClient.query({ query: LIVRE_QUERY, variables: { slug: query.titre } })
if (error) {
return <div>Erreur</div>
}
return { data, error, loading }
}

How can I navigate once when I found a JWT?

I am just getting stuck into react-native and need some help navigating to a protected screen when a token is found. Where should I look for a token on app load? And how can I navigate the user once without calling navigate multiple times? The problem I have is I am checking for a token on component mount, which is nested inside a stack. If I navigate to another part of the stack, the function is called again and I am unable to navigate. I can retrieve the token outside of the stack, but then I am having trouble navigating, as I need to pass props.navigate within a screen component. What is the recommended approach to finding a token, and making a navigation?
App.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import store from './store';
import RootContainer from './screens/RootContainer';
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<RootContainer />
</Provider>
);
}
}
RootContainer.js
...
render() {
const MainNavigator = createBottomTabNavigator({
welcome: { screen: WelcomeScreen },
auth: { screen: AuthScreen },
main: {
screen: createBottomTabNavigator({
map: { screen: MapScreen },
deck: { screen: DeckScreen },
review: {
screen: createStackNavigator({
review: { screen: ReviewScreen },
settings: { screen: SettingsScreen }
})
}
})
}
// Route Configuration for Initial Tab Navigator
}, {
// do not instantly render all screens
lazy: true,
navigationOptions: {
tabBarVisible: false
}
});
return (
<View style={styles.container}>
<MainNavigator />
</View>
);
}
}
WelcomeScreen.js
...
componentDidMount(){
this.props.checkForToken(); // async method
}
// Async Action
export const checkForToken = () => async dispatch => {
console.log("action - does token exist ?");
let t = await AsyncStorage.getItem("jwt");
if (t) {
console.log("action - token exists");
// Dispatch an action, login success
dispatch({ type: LOGIN_SUCCESS, payload: t });
} else {
return null;
}
}
// WelcomeScreen.js continued
componentWillRecieveProps(nextProps){
this.authComplete(nextProps);
}
authComplete(props){
if(props.token){
props.navigation.navigate('map'); // called again and again when I try to navigate from within the Bottom Tab Bar Component
}
}
render(){
if(this.props.appLoading){ // default True
return ( <ActivityIndicator />);
}
return ( <Text>WelcomeScreen</Text> );
}
const mapStateToProps = state => {
return {
token: state.auth.token,
appLoading: state.auth.appLoading // default True
};
};
export default connect(mapStateToProps, actions)(WelcomeScreen);
I would suggest, not to store navigation state in redux.
Just navigate when you found a token or the user logged in.
If you still want to use redux or simply want to react on props changes, then the way is to use some Redirect Component, and render it only when the token changed from nothing to something. You could read about such implementation from react-router here. I think there is no such implementation for React Navigation.
When you are using React Navigation, then I would suggest to look into the docs, because I think it solves the problem you have.
https://reactnavigation.org/docs/en/auth-flow.html

Categories