I have a getServiceSideProps to render a complex APP interacting with an API:
const MyApp: NextPage = (props: any) => {
let stripePromise: any;
const getStripe = async () => {
if (!stripePromise) {
stripePromise = await loadStripe(STRIPE_API_KEY);
}
return stripePromise;
};
const {t} = useTranslation(["generator"]);
return (
<>
<NextSeo
title={t("generator")}
description={t("generator_intro")}
canonical="url"
/>
<main style={{ minHeight: "100vh" }}>
<Elements stripe={getStripe()}>
<GeneratorHeader />
<Generator />
<GeneratorFAQ />
</Elements>
</main>
</>
)
}
export const getServerSideProps: GetServerSideProps = async ({ locale, req, res }) => {
res.setHeader(
'Cache-Control',
'public, s-maxage=86400'
);
return {
props: {
...await serverSideTranslations(locale as string, ['generator', 'common', 'footer']),
}
}
}
export default MyApp
MyApp does request to an API and serves a stripe checkout payment page.
But when analysing the page with lighthouse, I see that all the stripes libraries have a TTL of "1m" despite trying to override maxage.
I'm unsure if I understood the nextjs documentation wrongly? How do you effectively cache js librairies with nextjs + SSR?
It is not possible to cache Stripe.js locally in your build or in a browser. That library is hosted by Stripe and has to be loaded directly from their server even if you use loadStripe(). This is required for security reasons and to meet the requirements for PCI compliance.
Related
Having trobule implementing okta redirect with docusaurus using their documentation due to how docusaurus intiallly loads in routes. Can anyone provide any guidance on how to go about this?
https://github.com/okta/okta-react
Expected Behavior:
Initial path to load up redirects to okta and authenticates then returns back to webpage.
I ran into this same issue and saw your posting hoping for an answer. bummer. Then I dug around a little more. It's not fully implemented yet because I'm waiting on the okta creds from my administrators, but this got me to a permission denied screen (which is a good thing to me!)
Few things:
docusaurus currently uses react-router-dom v5. you need to specifically set that instead of defaulting to v6
src/pages/index.tsx (i'm using typescript) should allow you to setup a browserrouter there
react-router-dom package:
"react-router": "^5.3.3",
"react-router-config": "^5.1.1",
"react-router-dom": "^5.3.3"
src/pages/index.tsx - I updated the home component to have a DocusaurusHome component, then made Home hold the routing logic
import React from 'react';
import clsx from 'clsx';
import Link from '#docusaurus/Link';
import useDocusaurusContext from '#docusaurus/useDocusaurusContext';
import Layout from '#theme/Layout';
import HomepageFeatures from '#site/src/components/HomepageFeatures';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import styles from './index.module.css';
import { OktaAuth } from '#okta/okta-auth-js';
import { Security, LoginCallback } from '#okta/okta-react';
import { RequiredAuth } from '../components/RequiredAuth';
// file with client id
import clientId from '../Okta/OktaClientID';
// file with issuer url
import issuerUrl from '../Okta/OktaIssuerUrl';
const config = {
clientId: clientId,
issuer: issuerUrl,
redirectUri: `${location.protocol}//${location.host}/callback`,
scopes: ['openid', 'profile', 'email'],
pkce: true
};
const oktaAuth = new OktaAuth(config);
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container">
<h1 className="hero__title">{siteConfig.title}</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/intro">
Docusaurus Tutorial - 5min ⏱️
</Link>
</div>
</div>
</header>
);
}
/**
* Actual Docusaurus Home component
*/
function DocusaurusHome(): JSX.Element {
const {siteConfig} = useDocusaurusContext();
return (<Layout
title={`Hello from ${siteConfig.title}`}
description="Description will go into a meta tag in <head />">
<HomepageHeader />
<main>
<HomepageFeatures />
</main>
</Layout>)
}
/**
* component for react-router-dom browserrouter and okta auth
*/
export default function Home(): JSX.Element {
const restore = async (_oktaAuth: OktaAuth, originalUri: string) => {
window.location.replace(originalUri);
};
return (
<BrowserRouter>
<Security oktaAuth={oktaAuth} restoreOriginalUri={restore}>
<Switch>
<Route path='/callback'>
<LoginCallback
errorComponent={(err) => {
// eslint-disable-next-line no-console
console.error(err);
setTimeout(() => {
localStorage.removeItem('okta-token-storage');
window.location.replace(`${location.protocol}//${location.host}/`);
}, 2000);
return null;
}}/>
</Route>
<Route path='/'>
<RequiredAuth />
</Route>
<Route path='*'>
<DocusaurusHome />
</Route>
</Switch>
</Security>
</BrowserRouter>);
}
The RequiredAuth component originally returned an react-router-dom Outlet, which doesn't exist in v5. I think that a Route should suffice in it's place
import React, { useEffect } from 'react';
import { useOktaAuth } from '#okta/okta-react';
import { toRelativeUrl } from '#okta/okta-auth-js';
import { Route } from 'react-router-dom';
export const RequiredAuth: React.FC = () => {
const { oktaAuth, authState } = useOktaAuth();
useEffect(() => {
if (!authState) {
return;
}
if (!authState?.isAuthenticated) {
const originalUri = toRelativeUrl(window.location.href, window.location.origin);
oktaAuth.setOriginalUri(originalUri);
oktaAuth.signInWithRedirect();
}
}, [oktaAuth, !!authState, authState?.isAuthenticated]);
if (!authState || !authState?.isAuthenticated) {
return <></>; // loading screen before okta login
}
return (<Route />);
};
I would like to use apollo client error link to create a MUI snackbar displaying any error that is returned by graphql.
The setup is a Nextjs web app using Material-ui and Apollo Client.
From Apollo documentation on Error links an error link can be created which will handle errors returned by the graphql API. The Apollo error link can not render anything it self as it will return void.
import { onError } from "#apollo/client/link/error";
// Log any GraphQL errors or network error that occurred
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
My initial thought was to use hooks and context. Creating a hook which the Apollo error link can push notifications on and a context provider in my _app.ts.
However the Apollo client is not a functional component hence this will not work.
I did think about creating a function to handle onError callback in Apollo queries and mutations but it seems like a lot of work to put an onError function on each query/mutation.
This can be handled using context. You have to do some changes to your component hierarchy.
ContextProvider => ApolloProvider
Make sure both are used in different components. Otherwise, you will not able to access hooks.
Ex: You should be able to access the hook inside the Root component, where you can add ApolloProvider.
I've created an example hope that helps (not using apollo but you can add): https://stackblitz.com/edit/react-ts-m7swyo
import React, { createContext, useContext, useReducer } from "react";
import { render } from "react-dom";
import "./style.css";
const Context = createContext({});
const Root = () => {
const { state, dispatch } = useContext(Context);
return (
<ApolloProvider
client={
new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
// Do something and dispatch to context
// dispatch({ type: 'ERROR', error: graphQLErrors || networkError });
}),
]),
})
}
>
<App />
</ApolloProvider>
);
};
const reducer = (state, action) => {
if (action.type === "ERROR") {
return { ...state, ...action.payload };
}
return state;
};
const App = () => {
const [state, dispatch] = useReducer(reducer, {});
return (
<Context.Provider
value={{
state,
dispatch,
}}
>
<Root />
</Context.Provider>
);
};
render(<App />, document.getElementById("root"));
I built a simple reddit app with React-Redux and when I go to a post page like /posts/mlxft5 and refresh the page it says that the post is not found. I can't figure out how to fix it. Here's my code on codesandbox.
here's my app.js code
function App() {
return (
<Router >
<div className="App">
<div className="App-navbar">
<SubredditsList />
</div>
<Switch>
<Route exact path="/" component={StartMessage}/>
<Route exact path="/search" component={SearchPostsList}/>
<Route exact path="/posts" component={PostsList} />
<Route path="/posts/:postId" component={PostDetailRender}/>
<Route path="/search/:postId" component={SingleSearchPostRender}/>
<Redirect to="/" />
</Switch>
</div>
</Router>
)
}
export default App`
Right now you are only requesting posts from the API when we are on the /posts page for a subreddit. There are no requests initiated by /posts/mlxft. We need to add an additional thunk action that can fetch and store a single post from the id.
We don't want to fetch posts that are already in Redux when we click on a single post from the list on the /posts page. We we will use the condition setting of the createAsyncThunk function to conditionally cancel the fetch if the data already exists.
export const fetchSinglePost = createAsyncThunk(
"posts/fetchSingle",
async (postId) => {
const response = await fetch(
`https://api.reddit.com/api/info/?id=t3_${postId}`
);
const json = await response.json();
return json.data.children[0].data;
},
{
condition: (postId, { getState }) => {
const { posts } = getState();
if (posts.entities[postId]) {
return false;
}
}
}
);
You need to add additional cases in your reducer to handle this thunk. Note: if you use builder callback notation instead of reducer map object notation then you could combine your two "rejected" cases.
[fetchSinglePost.pending]: (state, action) => {
state.status = "loading";
},
[fetchSinglePost.fulfilled]: (state, action) => {
state.status = "succeeded";
postsAdapter.upsertOne(state, action.payload);
},
[fetchSinglePost.rejected]: (state, action) => {
state.status = "failed";
state.error = action.error.message;
}
Inside of your PostDetailRender component you need to dispatch the fetchSinglePost action. It's ok to dispatch it all cases because the thunk itself will cancel the fetching.
useEffect(() => {
dispatch(fetchSinglePost(postId));
}, [dispatch, postId]);
You could potentially have a status for each post rather than one for the whole slice. I explain how to do that in this answer.
Updated CodeSanbox
I also made some changes so that you don't fetch the same subreddit's posts more than once.
I'm working on a web app with NextJS, Apollo and React (hooks).
I have a form that asks the name of the visitor as the first step in a registration process.
When submitting the form the name will be saved in the Apollo cache and the visitor gets redirected to the next page.
import React, { useState } from 'react';
import Router , {useRouter} from 'next/router';
import { useApolloClient } from '#apollo/react-hooks';
const NameForm = props => {
const [name, setName] = useState("");
const client = useApolloClient();
const router = useRouter();
const handleSubmit = e => {
e.preventDefault();
if(!name) return;
client.writeData({ data: { name } });
router.push('/user/register');
}
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Naam</label>
<div>
<input type="text" id="name" name="name" value={name} onChange={e => setName(e.target.value)} />
<button type="submit" onClick={handleSubmit}>Get started</button>
</div>
</div>
</form>
)
}
export default NameForm;
The next page contains a more extensive form. When visitors come from the homepage, the name is already known and I want to get it back from the cache. I thought
import { gql } from 'apollo-boost';
import { useApolloClient } from '#apollo/react-hooks';
import AddUserForm from '../../components/forms/AddUserForm';
const GET_NAME = gql`
query GetName {
name #client
}`;
const AddUser = ({ name }) => (
<React.Fragment>
<AddUserForm name={name} />
</React.Fragment>
)
AddUser.getInitialProps = async ctx => {
const client = useApolloClient();
const name = await client.cache.readQuery({ query: GET_NAME });
return { name: name || '' };
}
export default AddUser;
I thought I could do this in the getInititialProps hooks are only allowed in the body of a functional component.
Because of the continuous development of next, react hooks and apollo I'm missing a tutorial/course about this and I find it difficult to find a right way to do this.
I hope someone here can help me further.
use apollo-client cache can lead you to some questions that really depends on the apollo-client's implementation and nextjs implementation.
If you open your app by entering the url to the browser address bar, Next.js will make requests (assuming the view need to fetch data) from server-side, then send to the client the rendered HTML.
Because apollo-client fetch then cache the data from server side, then the question is "Does Next.js send the apollo-client with its cache to client side for next request?"
You cannot sure about this unless you understand clearly about Next.js and apollo-client cache (about its implementation or how it works inside, if apollo cache data in-memory on server-side, you will fail if you go this way)
The answer is unsure because it depends on two stuffs at the same time. And maybe changed on the future!
So to deal with this problem, just use the Next.js way, it has designed a tunnel for data, it is the query on the url.
const NameForm = props => {
const [name, setName] = useState("");
const client = useApolloClient();
const router = useRouter();
const handleSubmit = e => {
e.preventDefault();
if(!name) return;
router.push(`/user/register?name=${name}`);
}
//render ...
}
import { useRouter } from 'next/router';
import AddUserForm from '../../components/forms/AddUserForm';
const AddUser = () => {
const router = useRouter();
return (
<React.Fragment>
<AddUserForm name={router.query.name} />
</React.Fragment>
)
}
export default AddUser;
If you want to send an object instead of a string?
const data = { name: "FoxeyeRinx", email: "foxeye.rinx#gmail.com" };
const base64 = btoa(JSON.stringify(data));
router.push(`/user/register?data=${base64}`);
const AddUser = () => {
const router = useRouter();
const base64 = router.query.data;
//decode base64 then parse it to js object
const data = JSON.parse(atob(base64));
return (
<React.Fragment>
<AddUserForm data={data}/>
</React.Fragment>
)
}
If you think the query is ugly and want to hide the query, use this guide: https://nextjs.org/learn/basics/clean-urls-with-dynamic-routing
We have implemented bot framework-webchat to create a bot. Currently, we handle the minimize and maximize with the event passed in component (code shown below) but the challenge occurs when I minimize and then maximize the chatbot I am seeing 'Unable to connect' message and then it flashes away and if after an hour-long later if we minimize and maximize I am getting 'Network interruption occurred, Reconnecting...' How do I keep webchat potentially automatically reconnect when I minimize and maximize Chabot.
MaximizeChatWndow () {
if (this.state.token === null &&
this.state.productService === null) {
return
}
this.setState({
directLine: this.createDirectLine()
}, () => {
this.setState({
minimized: false,
newMessage: false,
userId: 'User_' + Math.random
})
})
this.checkExpandFlag = true
}
The component:
render () {
const {
state: { minimized, store }
} = this
return (
<Row>
<Col md={12}>
<div>
{minimized ? (
<ChatDon
handleMaximizeButtonClick={this.handleMaximizeButtonClick}
/>
) : (
<ChatWin
handleMinimizeButtonClick={this.handleMinimizeButtonClick}
directLine={this.state.directLine}
userId={this.state.userId}
store={store}
/>
)}
</div>
</Col>
</Row>
)
}
It looks like you creating your directLine object in "MaximizeChatWndow()" which I think is the problem. In "MaximizeChatWndow()", you should be fetching your token and passing that to your web chat component. It is in the web chat component that you should use the token to call createDirectLine().
It appears that there have been various updates to the 06.recomposing-us/a.minimizable-web-chat sample. (The docs also look like they are out of date and no longer match the code). However, if comparing to the available sample code, you will want to do something like the following. Please look at the full code in the above link as I am only including the most relevant parts here.
When I tested, I had no issues with the conversation resetting or the network disconnecting/reconnecting.
MinimizableWebChat.js
import WebChat from './WebChat';
const MinimizableWebChat = () => {
[...]
const [token, setToken] = useState();
const handleFetchToken = useCallback(async () => {
if (!token) {
const res = await fetch('http://localhost:3500/directline/token', { method: 'POST' });
const { token } = await res.json();
setToken(token);
}
}, [setToken, token]);
[...]
return (
[...]
<WebChat
className="react-web-chat"
onFetchToken={handleFetchToken}
store={store}
styleSet={styleSet}
token={token}
/>
)
WebChat.js
const WebChat = ({ className, onFetchToken, store, token }) => {
const directLine = useMemo(() => createDirectLine({ token }), [token]);
[...]
useEffect(() => {
onFetchToken();
}, [onFetchToken]);
return token ? (
<ReactWebChat ...
);
};
Hope of help!