How to get browser information for react components in nextjs? - reactjs

I am writing a react component in nextjs that needs some information about the browser environment before it can be rendered. I know I can get this information from the user agent string after page load with a useEffect like so...
const MyComponent = () => {
const [browser, setBrowser] = useState(null);
useEffect(() => {
const ua = utils.getBrowserFromUA()
setBrowser(ua)
});
if (!browser) {
return <div>Loading...</div>
}
return (
<div>
<SpecialComponent browser={browser} />
</div>
);
};
Is it possible to get these values before the react component renders, before the pageLoad event perhaps? I really only need to calculate these values once and potentially share the values with other components. I can't find anything in the docs apart from running scripts with the beforeInteractive flag but I'm not sure how I would get the data to my component.

Related

What am I doing wrong with React context?

Quick context, I am making a character-building app (think D&D Beyond). Users can log in, use the builder to generate a character, and then later recall and level-up that character. I'm hosting the data on MongoDB and using Realm Web for CRUD on the database. Based on this guide, I'm using React Context to keep track of the logged-in user as they navigate the site. I'm trying to create a widget in the NavMenu that will allow me to see the logged-in user for dev purposes (later, I can make a "Welcome, [name]!"-type greeting), but I keep getting the error that "mongoContext.user" is null. This makes sense, as the Context is set to null with useState, but as soon as the page renders there is an init() function which anonymously logs in the user if they aren't already. I can't seem to understand why this is happening, even after looking at 4 different articles on how to use React Context. Code Snippets below:
App.js
const [client, setClient] = useState(null);
const [user, setUser] = useState(null);
const [app, setApp] = useState(new Realm.App({id: process.env.REACT_APP_REALM_APP_ID}));
//Anonymously logs in to mongoDB App instance on page render
useEffect(() => {
const init = async () => {
if (!user) {
setUser(app.currentUser ? app.currentUser : await app.logIn(Realm.Credentials.anonymous()));
}
if (!client) {
setClient(app.currentUser.mongoClient('mongodb-atlas'));
}
}
init();
}, [app, client, user]);
//Function to render a given component and pass it the mongoContext prop
const renderComponent = (Component, additionalProps = {}) => {
return (
<MongoContext.Consumer>
{(mongoContext) => <Component mongoContext={mongoContext} {...additionalProps} />}
</MongoContext.Consumer>
);
}
return (
<MongoContext.Provider value={{app, client, user, setClient, setUser, setApp}}>
<Layout>
<Switch>
<Route exact path="/" render={() => renderComponent(Home)} />
... other Routes ...
</Switch>
</Layout>
</MongoContext.Provider>
);
}
As outlined in the guide, each Route receives a render function which wraps it in the Context.Consumer component and sends context. I couldn't figure out if this was possible with the Layout component (since it is itself a component wrapper), so instead I used React.useContext() inside the component I need context for: (the component relationship is Layout -> NavMenu -> UserDetail)
UserDetail.jsx
const UserDetail = (props) => {
const mongoContext = React.useContext(MongoContext);
return (
<div>
<h1>Logged in as: {mongoContext.user.id}</h1>
</div>
);
}
This is the component which gives the "mongoContext.user is null" error. The other components seem to use context just fine, as I have done some Create operations with the anonymous user. I can see that it probably has something to do with how the other components are rendered with the Consumer wrapper, but according to every guide I've read, the React.useContext in the UserDetail component should work as well. Thanks for any help, this has completely stymied the project for now.

NextJS Zustand persist state

I have this github repo: https://github.com/salmanfazal01/next-firebase-starter
Basically I am creating a basic boilerplate with NextJS, Firebase and Zustand as a global state manager
I am unable to persist a few states like theme, user, etc in Zustand. After refreshing the window, it defaults back to the default theme.
I did try the persist middleware provided by zustand and even though that works, it causes content mismatch error from the server and client
Error: Hydration failed because the initial UI does not match what was rendered on the server.
How would I go about persisting the theme mode in the state without using other libraries like next-themes?
Desire:
You want to persist state when moving between different pages while using server side rendering (SSR) with next.js. For example, you want to show a user's profile picture as you move between pages.
Initial implementation:
You save the data into the user's browser's local storage. You are using Zustand to do this.
Problem:
You see an error saying that the client-side content does not match the server rendered html. Example from next.js:
# Unhandled Runtime Error
Error: Text content does not match server-rendered HTML. See more info here: [https://nextjs.org/docs/messages/react-hydration-error](https://nextjs.org/docs/messages/react-hydration-error)
The reason for this is that you are rendering html on the server with a particular variables (For example, user_name). When you then load your page on the client-side and load the user_name variable from your client-side local storage, this variable contains a different value compared to the server-side. This causes a mismatch, which the error message highlights (https://nextjs.org/docs/messages/react-hydration-error).
Solution:
When the client-side html differs, load the variable from the local storage after the component/page has first rendered the html. To solve this with next.js, load your data from local storage only in the useEffect hook (equivalent to onMounted in Nuxt/Vue).
Example with Jotai:
// store.js
import { atomWithStorage } from 'jotai/utils'
export const countAtom = atomWithStorage('countAtom', 0);
// pages/login.js
import {useAtom} from "jotai";
import {countAtom} from "../stores/store";
import { useEffect, useState} from "react";
export default function Login(props) {
const [count, setCount] = useAtom(countAtom); // This gets the data from the store.
const add1 = () => {
setCount((count) => count + 1);
};
const [displayedCount, setDisplayedCount] = useState(); // Set a local variable.
useEffect(() => {
setDisplayedCount(count); // Set the local variable from our store data.
}, [count])
return (
<div >
<Head>
<title>Login</title>
</Head>
<main>
<p>
{ displayedCount }
Hello from the login
<button onClick={add1}>Add 1</button>
</p>
</main>
</div>
)
}
// pages/index.js
import {useAtom} from "jotai";
import {countAtom} from "../stores/store";
import { useEffect, useState} from "react";
export default function Home(props) {
const [count, setCount] = useAtom(countAtom); // This gets the data from the store.
const add1 = () => {
setCount((count) => count + 1);
};
const [displayedCount, setDisplayedCount] = useState(); // Set a local variable.
useEffect(() => {
setDisplayedCount(count); // Set the local variable from our store data.
}, [count])
return (
<div >
<Head>
<title>Home</title>
</Head>
<main>
<p>
{ displayedCount }
Hello from the home page
<button onClick={add1}>Add 1</button>
</p>
</main>
</div>
)
}
I suppose you need to use useEffect() to transfer your Zustand states to useState() to prevent Hydration error.
Your Zustand State Data that you imported in any page / component needs to be updated inside useEffect() to new useState().
https://nextjs.org/docs/messages/react-hydration-error
This error is caused by zustand fetching data from persist middleware storage and updating its store before NextJS hydration complete in the beginning.
This can be simply avoided if you render any zustand store variable after first useEffect hook fired. But if you are using multiple variables inside multiple components then this could be tedious and bad solution.
To avoid it you can create a NextJS Layout and wait till first useEffect hook fire to render child elements.
import { useEffect, useState } from "react"
const CustomLayout = ({ children }) => {
const [isHydrated, setIsHydrated] = useState(false);
//Wait till NextJS rehydration completes
useEffect(() => {
setIsHydrated(true);
}, []);
return (
<>
{isHydrated ? ( <div>{children}</div> ) : <div>Loading...</div>}
</>
);
};
export default CustomLayout;
I ran into similar problem when storing opened state in Zustand for an Advent Calendar site. Each day's state is stored on the client and persisted with Zustand persist. To fix, I just followed the guidance from NextJS to use a Dynamic Import and disable SSR for that component.
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(() => import('../components/header'), {
ssr: false,
})
Source: NextJS Dynamic Imports
This error is caused by Nextjs SSR so all we have to do is set isSSR to true meaning we meaning we assume on the start our page is being server-side rendered and when its true return null.... so we use useEffect to set isSSR to false on client-side immediately the page gets mounted.
I hope this solves your problem...
import { useState, useEffect } from 'react'
const App = ({ Component, pageProps }: AppProps) => {
const [isSSR, setIsSSR] = useState(true);
useEffect(() => {
setIsSSR(false)
}, [])
if (isSSR) return null;
return (
<Component {...pageProps} />
)
}

My Stripe react components are not loading

MY purpose is to connect my react app with the Stripe using their components But the issue I am getting is my react components are not loading github link to project you can find relevant files of frontend frontend/src/screens/checkout/paymentWrap this file I am wrapping my form inside Element component provided by the stripe you can see everything infact I have push code with all credentials The error I get is
v3:1 Uncaught (in promise) IntegrationError: Please call Stripe() with your publishable key. You used an empty string.
at d (v3:1:52111)
at new e (v3:1:271693)
at ko (v3:1:303926)
at initStripe (stripe.esm.js:101:1)
at stripe.esm.js:125:1
Actually I follow their docs docs
but no where they have called this Stripe so I also have not I am frustrated a lot since three days I am here cant go forward
const [stripeApiKey, setStripeApiKey] = useState("");
I think this line at the top of the file is the reason you are running into that issue. Your code looks fine, but you're forgetting how the react lifecycle works.
The initial render of the WrapPayment.jsx component will try to call loadStripe("") because the initial state of stripeApiKey is ""
I'd suggest creating a loading state variable like so:
const [loading, setLoading] = useState(true);
Add in the setLoading(true); call here:
async function getStripeApiKey() {
const { data } = await axios.get("/api/v1/stripeapikey");
setStripeApiKey(data.stripeApiKey);
setLoading(false);
}
And render like so:
if(loading) {
return "Some Loader Component here"
} else {
return (
<>
{/* <Payment/> */}
<Elements stripe={loadStripe(stripeApiKey)}
options={options}
>
<Payment/>
</Elements>
</>
)
}

Ways to deal with React page flickering on re-renders (functional components)

A lot of things are causing my React functional components to flicker as new data comes in from various fetches and everything is constantly redrawn top-to-bottom.
For example, if I have a top-level App component which somewhere down the line includes a MyForm component, and the App does an initial fetch of DB Lookups which I depend on in the MyForm (passed as a prop, lookups={lookups}), the MyForm will flicker as it redraws itself on receiving the new prop values.
App
useEffect(() => {
fetchLookups();
}, []);
const fetchLookups = async () => {
const url = '/api/general/lookups';
try {
const responseLookups = await axios.get(url);
setLookups(responseLookups.data);
}
}
MyForm - will flicker - note also I have to check for the Non-NULL Fetch result from App, which is a pain
<Form.Control
value={props.lookups &&
props.lookups.filter(item => item.id === 30)
.map((item, index) => {
return (
item.description
);
})
/>
Also, in the MyForm component, there are lots of use inits/listeners with useEffect(.., []) and they will also cause the form to re-render. I have no way of knowing what the order will be on all those inits/listeners.
So my questions are, what's the best way to deal with this flickering?
Can I detect when a functional component is fully rendered, to show a loading spinner covering the full initialization sequence?
Can I block re-renders in any way?

Reuse Child Component 'Instance' Across Multiple Parents

I am currently building a Dashboard using React and D3. I'd like to compose each Dashboard as their own component (rather than have a single mega component manage everything) but re-use the child components within each Dashboard which will allow smooth animations and transitioning.
The following is a very rough example of what I'm trying to achieve.
const Dashboard1 = () => {
const data = useData('/main');
return <ChartComponent data={data}/>;
};
const SubDashboard = () => {
const data = useData('/sub');
return <ChartComponent data={data}/>;
};
const App = (props) => {
return props.topLevel ? <TopDashboard/> : <SubDashboard/>;
};
The issue I am finding with this approach is that <ChartComponent> will un-mount and re-mount when navigating between each Dashboard (due to the parents being different) which causing the page to flicker and all visuals to be redrawn which is expensive for some of the more complex visuals. What I would like to happen is for the same instance of <ChartComponent> to be used on both dashboards between each render so that it doesn't unmount, gets the new data passed into it and can animate from the previous values to the new ones.
Are there are ways or patterns to achieve this?
You need a parent item that stores all the data and passes it down to child components but I'd suggest to do this with redux.
const Dashboard1 = (props) => {
return <ChartComponent data={props.data.main}/>;
};
const SubDashboard = (props) => {
return <ChartComponent data={props.data.sub}/>;
};
const App = (props) => (
// this should be load elsewhere, store in redux and use it here once loaded
const data = {
main: useData('/main')
sub: useData('/sub'),
};
return props.topLevel ? <TopDashboard data={data} /> : <SubDashboard data={data} />;
};
you need to use this react reparating npm module to do that. React doesn't provide support for it in-built.
https://paol-imi.github.io/react-reparenting/

Resources