Exposing every component in Next.js app to custom hook on pageLoad - javascript

in my Next.js app, I have a react hook that fetches the currently authenticated user, and sets it to a piece of global state. I want to run this hook once on page load, but I want it to be exposed to every component in the app
import { useState, useEffect } from 'react';
import { useQuery } from '#apollo/client';
import { GET_AUTHED_USER } from '../utils/queries';
import { useAppContext } from '../context';
export const getCurrentUser = () => {
const [isCompleted, setIsCompleted] = useState(false)
const [state, setState] = useAppContext()
const { data: authedUserData } = useQuery(GET_AUTHED_USER, {
onCompleted: () => setIsCompleted(true)
});
useEffect(() => {
Router.push('/home')
if (isCompleted) {
setState({
currentUser: authedUserData?.getAuthedUser,
isAuthed: true,
});
}
}, [isCompleted]);
return [state, setState];
_APP.js
import '../styles/reset.css'
import { AppWrapper } from '../context'
import { getCurrentUser } from '../hooks';
function MyApp({ Component, pageProps }) {
const [state] = getCurrentUser()
console.log(state) // TypeError: Invalid attempt to destructure non-iterable instance.
return (
<AppWrapper>
<Component {...pageProps} />
</AppWrapper>
)
}
export default MyApp
the hook does work in pages/index.js but that means I can only run it if the / endpoint is hit.
<AppWrapper/> is where all the values get originally defined
import { createContext, useContext, useState } from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider, createHttpLink } from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
import { getCookie } from '../utils/functions';
const AppContext = createContext();
export function AppWrapper({ children }) {
const URI = 'http://localhost:5000/graphql';
const [state, setState] = useState({
currentUser: null,
isAuthed: false,
});
const httpLink = createHttpLink({
uri: URI,
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = getCookie('JWT');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? token : '',
}
}
});
const client = new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink)
});
return (
<AppContext.Provider value={[state, setState]}>
<ApolloProvider client={client}>
{children}
</ApolloProvider>
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}

Interesting question, you want to load that portion of code only once per each browser hit?
Then the location is right. NextJs make sure when you have a unique browser hit, it runs _app.js, but only once, after that it'll goes into a single page application mode.
After the above fact, actually whether a piece of code is run only once or twice or multiple time is mostly driven by how many times it detects the "change".
useEffect(() => {
// run
}, [condition])
If the condition changes, it'll run again. However if the condition does not change, but the whole piece is re-mount, it'll run again. You have to consider both fact here.
In short, if you have to run it per route change, make the condition === route.name. A piece of advice, try work with the single page application first, then work with the unique feature nextJS, because otherwise it'll be really difficult to figure out the answer.

Related

How to use the global data from React inside Apollo client's initialization?

When it comes to state centralization I know how to use the context api and Redux. But to recover that state we always have to be inside a react component.
What is the best strategy to access a global state/variable inside a common function that is not inside a react component?
In the environment variables is not an option because this value is changed after the application runs. And I didn't want to put in cookies or local storage for security reasons.
Index.ts
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import apolloClient from './services/apollo';
import { PersonalTokenProvider } from './providers/personal-token';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<PersonalTokenProvider>
<ApolloProvider client={apolloClient}>
<App />
</ApolloProvider>
</PersonalTokenProvider>
</React.StrictMode>,
document.getElementById('root'),
);
PresonalToken context provider
import React, { useState } from 'react';
interface ProviderProps {
children: JSX.Element[] | JSX.Element;
}
export const PersonalTokenContext = React.createContext({});
export const PersonalTokenProvider: React.FC<ProviderProps> = (
props: ProviderProps,
) => {
const [token, setToken] = useState<string | null>(null);
const { children } = props;
return (
<PersonalTokenContext.Provider value={{ token, setToken }}>
{children}
</PersonalTokenContext.Provider>
);
};
apollo client config
import { useContext } from 'react';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { PersonalTokenContext } from '../providers/personal-token';
//cant do this
const {token} = useContext(PersonalTokenContext)
const httpLink = new HttpLink({
uri: 'https://api.github.com/graphql',
headers: {
authorization: `Bearer ${token}`,
},
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
});
export default client;
Pure React Apollo client initialization
There are multiple ways to simulate a singleton to manage the Apollo client from within React. Here's one way using useRef to always have the latest token when making GraphQL queries and useMemo to only create the client once.
import {
ApolloClient,
createHttpLink,
InMemoryCache,
ApolloProvider
} from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
// The name here doesn't really matters.
export default function CustomApolloProvider(props) {
const { token } = useContext(PersonalTokenContext);
const tokenRef = useRef();
// Whenever the token changes, the component re-renders, thus updating the ref.
tokenRef.current = token;
// Ensure that the client is only created once.
const client = useMemo(() => {
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
authorization: tokenRef.current ? `Bearer ${tokenRef.current}` : '',
}
}));
const httpLink = createHttpLink({
uri: 'https://api.github.com/graphql',
});
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}, [])
return <ApolloProvider client={client} {...props} />;
}
Then in the app:
<PersonalTokenProvider>
<CustomApolloProvider>
<App />
</CustomApolloProvider>
</PersonalTokenProvider>
Pros:
Totally inside of React, which means it could use other hooks and data that changes from different places, like the locale code from the translation lib, etc.
One client per mounted application, which means, if the application needs to be unmounted, this solution would ensure proper cleanup.
Easy to add unit/integration tests
Cons:
A little more complex to put in place.
If not properly setup, multiple Apollo clients could end up being created, losing the previous cache, etc.
Using localStorage
The Apollo documentation suggests using the local storage to manage the authentication token.
import { ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
const httpLink = createHttpLink({
uri: '/graphql',
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Pros:
Simple to add to your existing implementation
There's ever only one client created for the entire lifetime of the app
The local storage is a good place to store global data across tabs, refresh, etc.
Cons:
Lives outside of React, so the app wouldn't re-render when the token changes, etc.
Could be harder/complex to unit test.
Using module scoped variable
Using a simple variable at the root of the module would be enough, you wouldn't even need the token context anymore.
import {
ApolloClient,
createHttpLink,
InMemoryCache,
makeVar
} from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
// module scoped var for the token:
let token;
// custom module setter:
export const setToken = (newToken) => token = newToken;
const httpLink = createHttpLink({
uri: '/graphql',
});
// Apollo link middleware gets called for every query.
const authLink = setContext((_, { headers }) => ({
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
));
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
Pros:
Simple to add to your existing implementation
There's ever only one client created for the entire lifetime of the app
Cons:
Lives outside of React, so the app wouldn't re-render when the token changes, etc.
Could be harder/complex to unit test
Lost when the user refreshes the page, or closes the app.
Reactive vars to manage the token
juanireyes suggested Apollo Reactive variables, but they're meant for a particular use-case, which is totally unnecessary to manage the token globally like we want here. It is similar to the module scope variable suggestion above, but with extra steps.
If you are trying to use Apollo I would personally encourage you to use the updated library: #apollo/client. Then you can use Reactive Variables to access the state from multiple places. Then you can try in your provider file something like this to access the token variable:
import React, { useState } from 'react';
import { makeVar } from '#apollo/client';
interface ProviderProps {
children: JSX.Element[] | JSX.Element;
}
export const tokenVar = makeVar<string | null>(null);
export const PersonalTokenContext = React.createContext({});
export const PersonalTokenProvider: React.FC<ProviderProps> = (
props: ProviderProps,
) => {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
tokenVar(token)
}, [token]);
const { children } = props;
return (
<PersonalTokenContext.Provider value={{ token, setToken }}>
{children}
</PersonalTokenContext.Provider>
);
};
And finally you can access the token value from everywhere calling tokenVar() or using the useReactiveVar hook.
You can access the content of the Redux store from outside of a component. I know two ways of doing so:
getState
Import the store from the file where you declare it, and access the whole state with the getState method:
import { store } from '../myReduxConfig.js';
const myFunc = () => {
const reduxData = store.getState();
}
subscribe
If you need the function to run again on redux store changes, import the store from the file where you declare it, and subscribe your function to it:
import { store } from '../myReduxConfig.js';
store.subscribe(myFunc);
const myFunc = () => {
const reduxData = store.getState();
}

Get logged user every time the component renders under React ContextAPI

I'm using ContextAPI in a small React project, I use HttpOnly Cookie to store the user's token when I hit the /login endpoint.
This is UserContext.js shown bellow, which encapsulates all the components (children) in App.js
import axios from "axios";
import { createContext, useEffect, useState } from "react";
const UserContext = createContext();
const UserContextProvider = ({ children }) => {
const [loggedUser, setLoggedUser] = useState(undefined);
const checkLoggedIn = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/logged-in`);
setLoggedUser(response.data);
};
useEffect(() => {
checkLoggedIn();
}, []);
return (
<UserContext.Provider value={{ loggedUser }}>
{children}
</UserContext.Provider>
);
};
export { UserContext };
export default UserContextProvider;
What I understand is when I log in, I setLoggedUser to the state from the /login response, and now it is available for all the children components of the context.
Now I can navigate to all components wrapped by the context and print for example the email of the loggedUser, but what if the email changed while we're logged in? I'll still see the old email on my components because the data is outdated in the state. And what if token got invalidated on the server while we were logged in.. (The only case we'll get updated data is if I refresh the app because that will trigger useEffect in the context provider and refresh the state again)
Should I also pass the checkLoggedIn function through the context's value property to make it available for other components and then use it in UseEffect in every component? Or is there a better solution for this problem?
After the latest comment if you want to check for email on every re-render then you can remove [] from useEffect as stated above in the comments by #abu dujana.
import axios from "axios";
import { createContext, useEffect, useState } from "react";
const UserContext = createContext();
const UserContextProvider = ({ children }) => {
const [loggedUser, setLoggedUser] = useState(undefined);
const checkLoggedIn = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/logged-in`);
setLoggedUser(response.data);
};
useEffect(() => {
checkLoggedIn();
});
return (
<UserContext.Provider value={{ loggedUser }}>
{children}
</UserContext.Provider>
);
};
export { UserContext };
export default UserContextProvider;

quick question about react context and rerendering components

I just started playing with context today and this is my usercontext
import { createContext, useEffect, useState } from "react";
import axios from "axios";
export const userContext = createContext({});
const UserContext = ({ children }) => {
const [user, setUser] = useState({});
useEffect(() => {
axios.get("/api/auth/user", { withCredentials: true }).then((res) => {
console.log(res);
setUser(res.data.user);
});
}, []);
return <userContext.Provider value={user}>{children}</userContext.Provider>;
};
export default UserContext;
this is how im using it in any component that needs the currently logged in user
const user = useContext(userContext)
my question is whenever the user logs in or logs out I have to refresh the page in order to see the change in the browser. is there any way that I can do this where there does not need to be a reload. also any general tips on react context are appreciated
(EDIT)
this is how Im using the UserContext if it helps at all
const App = () => {
return (
<BrowserRouter>
<UserContext>
<Switch>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
component={route.component}
/>
))}
</Switch>
</UserContext>
</BrowserRouter>
);
};
Where is your context consumer?
The way it is set up, any userContext.Consumer which has a UserContext as its ancestor will re render when the associated user is loaded, without the page needing to be reloaded.
To make it clearer you should rename your UserContext component to UserProvider and create a corresponding UserConsumer component:
import { createContext, useEffect, useState } from "react";
import axios from "axios";
export const userContext = createContext({});
const UserProvider = ({ children }) => {
const [user, setUser] = useState({});
useEffect(() => {
axios.get("/api/auth/user", { withCredentials: true }).then((res) => {
console.log(res);
// setting the state here will trigger a re render of this component
setUser(res.data.user);
});
}, []);
return <userContext.Provider value={user}>{children}</userContext.Provider>;
};
const UserConsumer = ({ children }) => {
return (
<userContext.Consumer>
{context => {
if (context === undefined) {
throw new Error('UserConsumer must be used within a UserProvider ')
}
// children is assumed to be a function, it must be used
// this way: context => render something with context (user)
return children(context)
}}
</userContext.Consumer>
);
};
export { UserProvider, UserConsumer };
Usage example:
import { UserConsumer } from 'the-file-containing-the-code-above';
export const SomeUiNeedingUserInfo = props => (
<UserConsumer>
{user => (
<ul>
<li>{user.firstName}</>
<li>{user.lastName}</>
</ul>
)}
</UserConsumer>
)
To be fair, you could also register to the context yourself, this way for a functional component:
const AnotherConsumer = props => {
const user = useContext(userContext);
return (....);
}
And this way for a class component:
class AnotherConsumer extends React.Component {
static contextType = userContext;
render() {
const user = this.context;
return (.....);
}
}
The benefit of the UserConsumer is reuasability without having to worry if you're in a functional or class component: it will used the same way.
Either way you have to "tell" react which component registers (should listen to) the userContext to have it refreshed on context change.
That's the whole point of context: allow for a small portion of the render tree to be affected and avoid prop drilling.

Next.JS Redux dispatch not working in getStaticProps()

I am pretty new to Next.JS and I was trying to set up Redux with my Next.JS application. Now my page is supposed to display a list of posts that I am calling in from an API. The page renders perfectly when I'm dispatching from useEffect() to populate the data on to my page, but getStaticProps() or getServerSideProps() are not working whatsoever!
Here is a bit of code that will give you a hint of what I've done so far:
store.js
import { useMemo } from 'react'
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import rootReducer from './reducers/rootReducer'
const initialState = {}
const middlewares = [thunk]
let store
function initStore(preloadedState = initialState) {
return createStore(
rootReducer,
preloadedState,
composeWithDevTools(applyMiddleware(...middlewares))
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
store = undefined
}
if (typeof window === 'undefined') return _store
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
action.js
export const fetchPosts = () => async dispatch => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts')
dispatch({
type: FETCH_POSTS,
payload: res.data
})
}
_app.js
import { Provider } from 'react-redux'
import { createWrapper } from 'next-redux-wrapper'
import { useStore } from '../redux/store'
export default function MyApp({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState)
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
These are the files that I needed for the basic redux setup. Once my store was set up and I wrapped my app around the Provider, I initially though of using useEffect() hook to populate data on a component that was rendering inside my index.js file.
component.js
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts } from '../redux/actions/postsAction'
const Posts = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.posts)
useEffect(() => {
dispatch(fetchPosts())
}, [])
return (
<div className="">
<h1>Posts</h1>
{items.map(post => {
return (<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>)
})}
</div>
)
}
export default Posts
This worked perfectly! All my posts were showing up inside the component. The problem occurred when I was trying to achieve the same behaviour with server side rendering (or even SSG). I wanted to populate the data during the pre-render phase but for some reason the items array which is supposed to hold all the data is empty, basically meaning that the disptacher was never called! Here is the piece of code that is bothering me (exactly same as previous code, but this time I'm using getStaticProps() instead of useEffect()):
component.js
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts } from '../redux/actions/postsAction'
const Posts = ({ items }) => {
return (
<div className="">
<h1>Posts</h1>
{items.map(post => {
return (<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>)
})}
</div>
)
}
export async function getStaticProps() {
console.log('Props called')
const dispatch = useDispatch()
const { items } = useSelector(state => state.posts)
dispatch(fetchPosts())
console.log(items)
return { props: { items } }
}
export default Posts
By running this, I'm getting an error that items is empty! Please help me, I have no clue what's going wrong here.
Well I fixed this issue myself but I forgot to post an answer for it, my bad!
The problem here really is very simple, hooks don't work outside of a functional component!
I think, inside of getStaticProps just call API or get datas from DB and returns it as props to pages/index.js (any component you want) and inside of this component we can get datas from getStaticProps as props.
Also we can set it as global state using useDispatch of react-redux. After that any component we can call those states using redux mapStateToProps. This is my solution.
This maybe a solution if anyone faced this problem,
import React from 'react';
import {useSelector} from 'react-redux';
import {wrapper} from '../store';
export const getStaticProps = wrapper.getStaticProps(store => ({preview})
=> {
console.log('2. Page.getStaticProps uses the store to dispatch things');
store.dispatch({
type: 'TICK',
payload: 'was set in other page ' + preview,
});
});
// you can also use `connect()` instead of hooks
const Page = () => {
const {tick} = useSelector(state => state);
return <div>{tick}</div>;
};
export default Page;
Got it from here: https://github.com/kirill-konshin/next-redux-wrapper

How do I implement Sibling Component communication in App shell and SSR

I have adopted a project that was built on this starter kit. This architecture employs App Shell and SSR. I am trying to add a simple search bar and this will mean passing the search keys from the search bar component to the post-list component so they can be filtered. I have found that this is nearly impossible with Context Providers and Consumers. I would like to use Context, but I do not know how to do it. It looks like this starter kit has this as a serious shortcoming and if it could be solved, it would make this kit more useful online.
If you look at the code below and in the link above, you can see that there is a header center and then thee are pages. I need a communication between the header and the pages. You can just use the code in the link to add the sibbling communication.
The use of Hydrate seems to preclude the simple application of a context provider. Hydrate adds components in a parallel way with no way to have the Context Provider above both of them. This pattern I am using here does not work. When I update the provider it does not cause a re-render of the context consumer.
If I have to use something other than Context, like say Redux, then I will accept that answer.
Here is the client entry point:
import { onPageLoad } from 'meteor/server-render';
import MeteorLoadable from 'meteor/nemms:meteor-react-loadable';
import { Switch, Route, Router, Redirect } from 'react-router-dom';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import apolloLogger from 'apollo-link-logger';
import { onTokenChange, getLoginToken } from '/app/ui/apollo-client/auth';
import createBrowserHistory from 'history/createBrowserHistory';
import 'unfetch/polyfill';
// Initialise react-intl
import { primaryLocale, otherLocales } from '/app/intl';
// Need to preload of list of loadable components for MeteorLoadable
import '/app/ui/loadables';
// To get started, create an ApolloClient instance and point it at your GraphQL
// server. By default, this client will send queries to the '/graphql' endpoint
// on the same host.
// To avoid asynchronously accessing local storage for every GraphQL request,
// we cache the authorisation token, and update it using an onTokenChange callback
let authToken;
let authTokenInitialised = false;
onTokenChange(({ token }) => { authToken = token; authTokenInitialised = true; });
const withAuthToken = setContext(() => {
if (authTokenInitialised) {
return authToken ? { headers: { authorization: authToken } } : undefined;
}
return getLoginToken()
.then((token) => {
authToken = token;
authTokenInitialised = true;
return authToken ? { headers: { authorization: authToken } } : undefined;
});
});
const resetAuthToken = onError(({ networkError }) => {
if (networkError && networkError.statusCode === 401) {
// Remove cached token on 401 from the server
authToken = null;
authTokenInitialised = false;
}
});
const onErrorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) => console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
));
}
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const client = new ApolloClient({
link: ApolloLink.from([
apolloLogger,
withAuthToken,
resetAuthToken,
onErrorLink,
new HttpLink({
uri: '/graphql',
}),
]),
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
});
// Inject the data into the app shell.
// If the structure's changed, ssr.js also needs updating.
async function renderAsync() {
const [
React,
{ hydrate, render },
{ default: App },
{ default: HeaderTitle },
{ default: LanguagePicker },
{ default: Routes },
{ default: Menu },
] = await Promise.all([
import('react'),
import('react-dom'),
import('/app/ui/components/smart/app'),
import('/app/ui/components/smart/header/header-title'),
import('/app/ui/components/dumb/language-picker'),
import('/app/ui/routes'),
import('/app/ui/components/smart/menu'),
MeteorLoadable.preloadComponents(),
]);
// Given that we are implementing App Shell Architecture and, therefore,
// injecting (via reactDOM.render) the Header, Menu and Main components into
// different HTML elements, we need a way to share the router 'history' among
// all three mentioned components.
// As a default, for every invocation of 'BrowserRouter', there will be new
// 'history' instance created. Then, changes in the 'history' object in one
// component won't be available in the other components. To prevent this, we are
// relying on the 'Router' component instead of 'BrowserRouter' and defining our
// custom 'history' object by means of 'createBrowserHistory' function. Said
// 'history' object is then passed to every invocation of 'Router' and therefore
// the same 'history' object will be shared among all three mentioned components.
const history = createBrowserHistory();
// Inject react app components into App's Shell
const ClientApp = ({ component }) => (
<Router history={history}>
<ApolloProvider client={client}>
<Switch>
{/* Map our locales to separate routes */}
{ otherLocales.map(locale => (
<Route
key={locale}
path={`/${locale}/`}
render={props => <App component={component} {...props} locale={locale} section="app" />}
/>
))}
{ primaryLocale && (
<Route
key={primaryLocale}
path="/"
render={props => <App component={component} {...props} locale={primaryLocale} section="app" />}
/>
)}
{/* If no valid locale is given, we redirect to same route with the preferred locale prefixed */}
<Route render={({ location }) => <Redirect to={`/${window.__PREFERRED_LOCALE__ || otherLocales[0]}${location.pathname}`} />} />
</Switch>
</ApolloProvider>
</Router>
);
render(<ClientApp component={Menu} />, document.getElementById('menu'));
hydrate(<ClientApp component={HeaderTitle} />, document.getElementById('header-title'));
hydrate(<ClientApp component={LanguagePicker} />, document.getElementById('header-lang-picker'));
hydrate(<ClientApp component={Routes} />, document.getElementById('main'));
}
onPageLoad(() => {
const renderStart = Date.now();
const startupTime = renderStart - window.performance.timing.responseStart;
console.log(`Meteor.startup took: ${startupTime}ms`);
// Register service worker
import('/app/ui/register-sw').then(() => {});
renderAsync().then(() => {
const renderTime = Date.now() - renderStart;
console.log(`renderAsync took: ${renderTime}ms`);
console.log(`Total time: ${startupTime + renderTime}ms`);
});
});
You can create a Context, lets'say AppContext
// you can create some help such as the AppProvider and the useAppContext hook
import { createContext, useContext, useState } from "react";
export const AppContext = createContext({});
export const AppProvider: any = ({ initialState = { search: "" }, children }) => {
const [search, setSearch] = useState(initialState);
return (
<AppContext.Provider value={[search, setSearch]}>
{children}
</AppContext.Provider>
)
};
export const useAppContext: any = () => useContext(AppContext);
then you can plug your provider at same level of apolloProvider
...
import {AppProvider} from './context/AppProvider';
....
<AppProvider> // optionally you can pass an initialState to you context as prop
<ApolloProvider client={client}>
// your code
</ApolloProvider>
</AppProvider>
...
and finally you can use the state (search) defined in your AppContext
import {useAppContext} from './context/AppProvider';
....
export const Component = () => {
const [search, setSearch] = useAppContext()
return <div>{search}</div>
}

Categories