Etherjs "window" object not accessible in Next.js - reactjs

I have a relatively basic project using solidity and react working as a single page dApp using Create React App. However I am trying to move this now to Nextjs and have hit a hurdle which I assume is something to do with the server side stuff Nextjs does. I have removed all the redundant code and just provide enough to generate the error:
import { ethers, Contract } from 'ethers';
import Project from '../src/artifacts/contracts/Project.sol/Project.json';
const contractAddress = process.env.contract_address;
export default function App() {
const provider = new ethers.providers.Web3Provider(window.ethereum);
console.log(provider.getSigner())
return (
<div className="App">
<p>Hello!</p>
</div>
);
}
This errors with:
window is not defined
I saw someone else suggest loading and setting it via state like so:
const [provider, setProvider] = useState({})
React.useEffect(() => {
setProvider(new ethers.providers.Web3Provider(window.ethereum))
}, []);
const signer = provider.getSigner();
But this returns: TypeError: provider.getSigner is not a function
However if i comment out this code, refresh and let the page load, then uncomment the code and let hot reload refresh the component I get no such error and can successfully console.log the signer.
Pulling my limited supply of hair out trying to resolve this, any help would be appreciated.

I have managed to get this working whilst sticking with my functional components.
Within useEffect I included a statement to check if the windowobject was undefined:
if (typeof window.ethereum !== "undefined" || (typeof window.web3 !== "undefined")) {
// Existing code goes here
}
And then had to make sure that any variables that I wanted to use outside of this if statement were saved to state as well as declared within the statement. Like:
const provider = new ethers.providers.Web3Provider(window.ethereum);
setProvider(provider)
This seemed to solve most of the issues with moving from CRA to Next and I now have the dApp back up and running.

Related

Update JSS sheet on specific redux state subtree change

I am injecting JSS into a manually loaded iframe, and then launching a reload whenever certain values in my redux state change, as follows:
export default function ETFStyleUpdater({ sheet, id }: Props) {
const readerConfig = useAppSelector((state) => state.bookReader[id] || DEFAULT_BOOK_READER_CONFIG_STATE);
useEffect(() => {
sheet.update(readerConfig);
}, [readerConfig]);
return <></>;
}
The react code is loaded as follows:
const container = document.body.appendChild(document.createElement("div"));
createRoot(container!).render(
<Provider store={store}>
<ETFStyleUpdater sheet={sheet} id={bookId} />
</Provider>,
);
While this felt nasty even when I wrote it, it worked fine with webpack. I am now moving to vitejs, which complains with a
Uncaught Error: #vitejs/plugin-react can't detect preamble. Something is wrong.
This all gets loaded dynamically into an iframe but by accessing the store via a property on window.parent, it worked just fine with webpack.
How can I get the same effect without creating a new react root and doing a nasty hack with useEffect? I tried to put a reference to the sheet onto window and use a redux-toolkit extraReducer (or middleware) but it doesn't seem to update, even though there is a reference there.
redux-watch seems to do the trick.
let w = watch(store.getState, "bookReader");
store.subscribe(
w((newVal) => {
sheet.update(newVal[bookId]);
}),
);

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>
</>
)
}

Next js how to fetch localStorage data before client side rendering

I am using react + next.js in my client side and I am getting this weird warning, which I've failed to fix over the previous 3 days.
I assume the Warning arise in because the isUserAuthenticated value is saved on the localStorage.
and localStorage is only available on the client side, and therefore the value is diffrent from the initial data rendered on the server.
after searching on google I read a few post which suggested using componentDidMount() to in order to load the localStorage values before the the client side in rendred.
unfortunelty I've failed to implement this idea, threfore I am asking for help, can someone help me to to solve this problem? thanks in advance
The warning:
react-dom.development.js:67 Warning: Text content did not match.
Server: "false" Client: "true"
at div
at ul
at div
at nav
at Navbar
Navbar.js
const Navbar = () => {
const { isUserAuthenticated, } = useSelector((state) => state.authReducer);
return (
<nav data-testid='navbar'>
<div>
<>{isUserAuthenticated ? <div>true</div> : <div>false</div>}</>
</div>
</nav>
);
};
export default Navbar;
the solution
// TODO recipes crud to navbar
// test author and guest links
import { useSelector } from 'react-redux';
import React from 'react';
const Navbar = () => {
const [isUserAuthenticated, setIsUserAuthenticated] = useState();
useEffect(() => {
setIsUserAuthenticated(store.getState().authReducer.isUserAuthenticated);
}, []);
return (
<nav data-testid='navbar'>
<div>
<>{isUserAuthenticated ? <div>true</div> : <div>false</div>}</>
</div>
</nav>
);
};
export default Navbar;
This error happens when your server returns a different html structure than your client side, this will force React to re-render the entire App instead just attach event listeners (which is much faster).
From the error, looks like your isUserAuthenticated & loggedUserData have different values at client side & server side, Print them, if this is the situation, check the reason for this.
Edit
As you've mentioned, localStorage available only at client side, this means that your code most support it.
At the serverSide (there is no localStorage) your isUserAuthenticated should return false.
At the client side, you should "continue" the server state (which means that you don't load the value from localStorage firstly), then when the app is mounted, load the value.
if you are using classes, use the componentDidMount (which runs only at client side) in order to update the value of isUserAuthenticated.
if you are using hooks, use the useEffect hook (which runs only at client side) in order to update the value of isUserAuthenticated.

React router provides javascript object instead of original type on url address bar reload

I am explining my problem with just the relevant code, as the full example is in this codesandbox link.
I am passing some props through a link to a component.
These props, have a firebase timestamp.
The props are passed correctly when the component is called through the link.
Link:
<Link to={{
pathname:path,
state: {
project
},
}} key={project.id}>
<ProjectSummary project={project} deleteCallback={projectDelete}/>
</Link>
Route:
<Route
path='/project/:id'
render={({ location }: {location: Location<{project: IFirebaseProject}>}) => {
const { state } = location;
const returnedComponent = state ? <ProjectDetails project={state.project} /> :
<ProjectDetails project={undefined}/>;
return returnedComponent;
}}
/>
and received by the ProjectList component, like this:
<div>{moment(stateProject.createdAt.toDate()).calendar()}</div>
My problem is that when the component is called through the link, props are passed and everything works fine, but, when I re-enter in the url adress bar, as the access to the component is not through the link, I would expect that the Route's render returned an undefined project (check route:
const returnedComponent = state ? <ProjectDetails project={state.project} /> : <ProjectDetails project={undefined}/>;) but, it returns the last passed project, with the timestamp as a plain Javascript object instead of a Timestamp type. So I get the error:
TypeError: stateProject.createdAt.toDate is not a function
Because the toDate() function is not available in the plain Javascript object returned, it is the Timestamp firebase type. Seems that for this specific case, the router is keeping it as a plain js object, instead of the original Timestamp instance. I would expect the route to return always the proyect undefined if not called from the link, as the props are not passed in (supposedly), but its not the case on the reload from the url address bar.
Curiously, in the codesandbox project, it does not reproduce, it fetches the data (you will be able to see the console.log('project fetched!!') when the project received is undefined).
However thrown from the dev server it happens. Might have something to do.
Find the git url if you wish to clone and check: https://github.com/LuisMerinoP/my-app.git
Remember that to reproduce you just need to enter to the link, and then put the focus in the explorer url address bar en press enter.
I case this might be the expected behaviour, maybe there is a more elegant way to way to deal with this specific case instead of checking the type returned on the reload. I wonder if it can be known if it is being called from the address bar instead of the link.
I know I can check the type in my component and fix this, creating a new timeStamp in the component from the js object returned, but I do not expect this behaviour from the router and would like to understand what is happenning.
Problem: Non-Serializable State
It returns the last passed project, with the timestamp as a plain Javascript object instead of a Timestamp type
I do not expect this behaviour from the router and would like to understand what is happening.
What's going on is that the state is being serialized and then deserialized, which means it's being converted to a JSON string representation and back. You will preserve any properties but the your methods.
The docs should probably be more explicit about this but you should not store anything that is not serializable. Under the hood React Router DOM uses the browser's History API and those docs make it more clear.
Suggestions
as in typescript is an assertion. It how you tell the compiler "use this type even though it's not really this type". When you have something that really is the type then do not use as. Instead apply a type to the variable: const project: IFirebaseProject = {
Your getProjectId function to get an id from a URL is not necessary because React Router can do this already! Use the useParams hook.
Don't duplicate props in state. You always want a "single source of truth".
Fetching Data
I played with your code a lot because at first I thought that you weren't loading the project at all when the page was accessed directly. I later realized that you were but by then I'd already rewritten everything!
Every URL on your site needs to be able to load on its own regardless of how it was accessed so you need some mechanism to load the appropriate project data from just an id. In order to minimize fetching you can store the projects in the state of the shared parent App, in a React context, or through a global state like Redux. Firestore has some built-in caching mechanisms that I am not too familiar with.
Since right now you are using dummy placeholder data, you want to build a way to access the data that you can later replace your real way. I am creating a hook useProject that takes the id and returns the project. Later on just replace that hook with a better one!
import { IFirebaseProject } from "../types";
import { projects } from "./sample-data";
/**
* hook to fetch a project by id
* might initially return undefined and then resolve to a project
* right now uses dummy data but can modify later
*/
const useProject_dummy = (id: string): IFirebaseProject | undefined => {
return projects.find((project) => project.id === id);
};
import { IFirebaseProject } from "../types";
import { useState, useEffect } from "react";
import db from "./db";
/**
* has the same signature so can be used interchangeably
*/
const useProject_firebase = (id: string): IFirebaseProject | undefined => {
const [project, setProject] = useState<IFirebaseProject | undefined>();
useEffect(() => {
// TODO: needs a cleanup function
const get = async () => {
try {
const doc = await db.collection("projects").doc(id).get();
const data = doc.data();
//is this this right type? Might need to manipulate the object
setProject(data as IFirebaseProject);
} catch (error) {
console.error(error);
}
};
get();
}, [id]);
return project;
};
You can separate the rendering of a single project page from the logic associated with getting a project from the URL.
const RenderProjectDetails = ({ project }: { project: IFirebaseProject }) => {
return (
<div className="container section project-details">
...
const ProjectDetailsScreen = () => {
// get the id from the URL
const { id } = useParams<{ id: string }>();
// get the project from the hook
const project = useProject(id ?? "");
if (project) {
return <RenderProjectDetails project={project} />;
} else {
return (
<div>
<p> Loading project... </p>
</div>
);
}
};
Code Sandbox Link

ReferenceError: window is not defined after installing my npm package containing a D3 chart on my Next.js repo

I have 2 repos, one that houses all my components that gets packaged and uploaded to a private feed, and one that consumes them. Both repos use React and TS with the repo that consumes the package also running Next.js
I built a charting component using Plottable.js and D3 in the components repo.
Upon installing the latest version of my package onto my main repo, I'm getting build errors that say,
ReferenceError: window is not defined
at Object.StiW (...\source\repos\njs-app\.next\serverless\pages\post.js:47656:19)
at __webpack_require__ (...\source\repos\njs-app\.next\serverless\pages\post.js:23:31)
Going to the first line that it mentions:
var nativeArray = window.Array;
The call to the function that renders the graph and my only call to window within the function that generates the graph
export const ChartComponent = ({data, currentValue}: PlotProps) => {
const makeBasicChart = () => {
...
...
...
if (typeof window !== "undefined") {
window.addEventListener("resize", () => plots.redraw());
}
}
React.useEffect(() => {
makeBasicChart();
});
return (
<ChartContainer id="Chart"/>
);
}
I'm receiving these errors without even including the component on a page.
I'm compiling my components repo with the TS compiler tsc and haven't had any issues until I included the charting component.
What do I need to do to resolve this issue?
The best solution for this is to make dynamic loading.
This way your component won't even be rendered on the server-side at all.
For example:
import dynamic from 'next/dynamic';
const OrganizationChart = dynamic(() => import('#dabeng/react-orgchart'), { ssr: false });
Check if window is defined before you call it
Next.js renders server side so this means that there's no window object.
You should wrap any code that uses window in this check
if (typeof window !== "undefined") {
// code that uses window
}
Window will also exists any time a component is mounted so you can use window in actions like onClick and also the hook useEffect
On SSR window is not available because its a browser based property, so its giving error.
You can avoid that error by doing this.
if (process.browser) {
// Your window based work.
}
For D3 based work you can initiate once the dom has ready. Better use useEffect

Resources