How to properly implement MoralisDappProvider as a React component? - reactjs

There's some code in the documention of Moralis and at github, but it no longer works.
For instance:
import React, { useEffect, useState, useMemo } from "react";
import { useMoralis } from "react-moralis";
import MoralisDappContext from "./context";
function MoralisDappProvider({ children }) {
const { Moralis, user, web3, enableWeb3, isWeb3Enabled } = useMoralis();
// const web3Provider = /*await*/ Moralis.enableWeb3(); //needed at all?
useEffect(() => {
Moralis.onChainChanged(function (chain) {
setChainId(chain);
});
Moralis.onAccountChanged(function (address) {
setWalletAddress(address[0]);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// doesn't work
useEffect(() => setChainId(web3.givenProvider?.chainId)); // "givenProvider" doesn't exist
//.....
Not to mention that there's no code as a whole, but there're only pieces of code: one here, other there.... and some don't work. How to glue them, after all?
The givenProvider? property doesn't even exist.
Is there code that works?
How to, generally, properly implement MoralisDappProvider component?

Related

Reproducable asynchronous bug found in #testing-library/react

Unless I'm mistaken, I believe I've found a bug in how rerenders are triggered (or in this case, aren't) by the #testing-library/react package. I've got a codesandbox which you can download and reproduce in seconds:
https://codesandbox.io/s/asynchronous-react-redux-toolkit-bug-8sleu4?file=/README.md
As a summary for here, I've just got a redux store and I toggle a boolean value from false to true after some async activity in an on-mount useEffect in a component:
import React, { useEffect } from "react";
import { useAppDispatch } from "../hooks/useAppDispatch";
import { setMyCoolBoolean } from "../redux/slices/exampleSlice";
import AnotherComponent from "./AnotherComponent";
export default function InnerComponent() {
const dispatch = useAppDispatch();
const fetchSomeData = async () => {
await fetch("https://swapi.dev/api/people");
dispatch(setMyCoolBoolean(true));
};
// on mount, set some values
useEffect(() => {
fetchSomeData();
}, []);
return <AnotherComponent />;
}
Then, in a different component, I hook into that store value with useAppSelector hook and then useEffect to do something local there (dumb example, but it illustrates my point.):
import { useEffect, useState } from "react";
import { useAppSelector } from "../hooks/useAppSelector";
export default function AnotherComponent() {
const { myCoolBoolean } = useAppSelector((state) => state.example);
const [localBoolean, setLocalBoolean] = useState(false);
// when myCoolBoolean changes, set the local boolean state value in this component
// somewhat a dumb example but it illustrates
// the failure of react-testing-library
useEffect(() => {
// only do something in this component
// if myCoolBoolean changes to true
if (myCoolBoolean) {
console.log("SET TO TRUE!");
setLocalBoolean(myCoolBoolean);
}
}, [myCoolBoolean]);
if (localBoolean) {
return <span data-testid="NEW">I'm new</span>;
}
return <span data-testid="ORIGINAL">I'm original</span>;
}
In result, my test would like to see if the 'new' value is ever shown. Despite issuing rerender, you will see the test fails:
import React from "react";
import { render } from "#testing-library/react";
import App from "../../src/App";
import { act } from "react-test-renderer";
import 'whatwg-fetch'
test("On mount, boolean value changes, causing our new span to show up", async () => {
const { getByTestId, rerender } = render(<App />);
await act(async () => {
// expect(getByTestId("ORIGINAL")).toBeTruthy();
// No matter how many times you call rerender here,
// you'll NEVER see the "NEW" test id (and thus corresponding <span> element) appear in the document
// despite this being the case in any standard browser
await rerender(<App />);
// If you comment this line below out, the test passes fine.
// test ID "ORIGINAL" is found, but "NEW" is never found!!!!
expect(getByTestId("NEW")).toBeTruthy();
});
});
Behaviour is totally as expected in a browser, but fails in my jest test. Can anybody guide me on how to get my test to pass? As far as I know, the code and implementations of my React components and Redux are the cleanest and best practices that are currently out there, so I'm more expecting this is a gross misunderstanding on my part of how #testing-library works, though I thought rerender would do the trick.
I've apparently misunderstood how react-testing-library works under the hood. You don't even need to use rerender or act at all! Simply using a waitFor with await / async is enough to trigger the on mount logic and subsequent rendering:
import React from "react";
import { findByTestId, render, waitFor } from "#testing-library/react";
import App from "../../src/App";
import { act } from "#testing-library/react-hooks/dom";
import "whatwg-fetch";
test("On mount, boolean value changes, causing our new span to show up", async () => {
const { getByTestId, rerender, findByTestId } = render(<App />);
// Works fine, as we would expect
expect(getByTestId("ORIGINAL")).toBeTruthy();
// simply by using 'await' here, react-testing-library must rerender somehow
// note that 'act' isn't even used or needed either!
await waitFor(() => getByTestId("NEW"));
});
Another case of "overthinking it" gone bad...

Invalid hook call. Hooks can only be called inside of the body of a function component when i call useQuery in useEffect

I am using apollo-graphql in my react project and i am getting error of
Invalid hook call. Hooks can only be called inside of the body of a function component
Here is my code for this
import React, { useEffect, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
// **************** COMPONENTS ****************
import { GET_MORTGAGE_JOURNEY } from "../../../../Graphql/Journeys/query";
export default function index() {
const insuranceId = useSelector((state) => state.mortgage.insuranceId);
// Panels Heading to show on all panels
useEffect(() => {
if (insuranceId) {
getMortgageData(insuranceId);
}
}, [insuranceId]);
function getMortgageData(insuranceId) {
const { loading, error, data } = useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
});
console.log(data);
}
return <section className="mortage-journey"></section>;
}
Once i run this i get the error, I know that useQuery itself is a hook and i cant call it from inside useEffect, but then what should be the workaround for this as i need insuranceId from my redux state first and send it to the query.
Thanks !
You are breaking the rule of hooks when you call it from any place other than the top level of a React component.
useEffect takes a callback function, and you are calling your hook from that. It is a problem.
I found this skip option in useQuery which helps you call useQuery conditionally.
useEffect(() => {
const { loading, error, data } =
useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
skip : (!insuranceId)
});
}, [insuranceId]);
Any time insuranceId changes your callback runs, so it is run after mount once and then on subsequent changes.
Try using refetch. Something like this:
const { data, refetch } = useQuery(MY_QUERY);
useEffect(() => {
refetch();
}, id);
You can use refetch wherever you like, in useEffect or a button click onclick handler.
Alternatively, you could use useLazyQuery if you don't want it to run the first time:
const [goFetch, { data }] = useLazyQuery(MY_QUERY);
Now you can use goFetch or whatever you want to call it wherever you like.
Your whole example might look like:
import React, { useEffect, useState, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { GET_MORTGAGE_JOURNEY } from "../../../../Graphql/Journeys/query";
export default function index() {
const insuranceId = useSelector((state) => state.mortgage.insuranceId);
const { loading, error, data, refetch } = useQuery(GET_MORTGAGE_JOURNEY, {
variables: { id: insuranceId },
});
useEffect(() => {
if (insuranceId) {
refetch({id: insuranceId});
}
}, [insuranceId]);
return <section className="mortage-journey"></section>;
}

React Maximum update depth exceeded error caused by useEffect only in test

I am trying to write some tests to my react component using jest and react-testing-library.
My component looks like:
//DocumentTable.js
import {useTranslation} from "react-i18next";
import ReactTable from "react-table-6";
import {connect} from "react-redux";
...
export const DocumentTable = ({documents, getDocuments, ...}) => {
const {t} = useTranslation();
const [data, setData] = useState([])
useEffect(() => {
getDocuments();
}, [])
useEffect(() => {
setData(() => translate(documents.map(doc => Object.assign({}, doc))))
}, [documents, t])
const translate = (tempDocuments) => {
if (tempDocuments[0]) {
if (tempDocuments[0].name) {
tempDocuments.forEach(doc => doc.name = t(doc.name));
}
if (tempDocuments[0].documentStatus) {
tempDocuments.forEach(doc => doc.documentStatus = t(doc.documentStatus));
}
}
return tempDocuments;
}
...
return (
<div className="col m-0 p-0 hidden-overflow-y">
<ReactTable
className="bg-dark dark-table"
data={data}
...
)
}
...
export default connect(mapStateToProps, matchDispatchToProps)(DocumentTable);
As you can see I am using redux and translation from react-i18next.
I am using this component to show values received from prop documents in ReactTable component from react-table-v6. To avoid editing my original value I create deep copy of documents array, translate it and put it into data which is used directly in my table.
I have started write my test from check if I can render my component properly using react-testing-library:
//DocumentTable.test.js
import React from 'react'
import {render} from '#testing-library/react'
import {DocumentTable} from "../../../components/content/DocumentTable";
import {I18nextProvider} from "react-i18next";
import i18n from "../../../i18n";
it("Should render component", () => {
const documents = [
{
name: "favourite",
documentStatus: "new"
},
{
name: "simple",
documentStatus: "edited"
}
]
render(
<I18nextProvider i18n={i18n}>
<DocumentTable documents={documents} getDocuments={jest.fn()}/>
</I18nextProvider>
);
})
and everything seems to work fine. However I want to use mock of useTranslation hook as I did in my other components tests.
My mock is:
//_mocks_/react-18next.js
module.exports = {
useTranslation: () => ({
t: key => key,
i18n: {}
}),
}
To use it I have added property to jest config:
//package.json
"jest": {
"moduleNameMapper": {
"react-i18next": "<rootDir>/src/tests/_mocks_/react-i18next.js"
}
},
and I have simplified my test:
//DocumentTable.test.js
import React from 'react'
import {render} from '#testing-library/react'
import {DocumentTable} from "../../../components/content/DocumentTable";
it("Should render component", () => {
const documents = [
{
name: "favourite",
documentStatus: "new"
},
{
name: "simple",
documentStatus: "edited"
}
]
render(
<DocumentTable documents={documents} getDocuments={jest.fn()}/>
);
})
and now when I run my test I get following error:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
in DocumentTable (at DocumentTable.test.js:89)
And I dont understand whats going on. I have come to conclusion that problem is caused by my useEffect hook in DocumentTable.js file. When I don't create copy of my props but translate it directly:
useEffect(() => {
setData(() => translate(documents))
}, [documents, t])
everything again works fine. But I must stay with creating copy of it(when user change language I want to translate again original documents).
How can I deal with that?
Thanks in advance.
The problem is that your mock will return a new function t each time, which will trigger the useEffect in you component since t is a dependency.
Use
//_mocks_/react-18next.js
const t = key => key;
module.exports = {
useTranslation: () => ({
t,
i18n: {}
}),
}

transitioning to use redux with hooks

figuring out how to use redux with hooks using this way but not sure its the correct way
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPins } from "../../../actions/pins";
function MainStory() {
const pins = useSelector(state => state.pins.pins);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPins(pins));
}, []);
console.log(pins);
return(<div> test </div>
would the above way be the right way to go despite missing dependencies?
React Hook useEffect has missing dependencies: 'dispatch' and 'pins'. Either include them or remove the dependency array
with components (the way i had it before)
import { getPins } from "../../actions/pins";
import { connect } from "react-redux";
import PropTypes from "prop-types";
export class Pins extends Component {
static propTypes = {
pins: PropTypes.array.isRequired,
getPins: PropTypes.func.isRequired
};
componentDidMount() {
this.props.getPins();
}
render() {
console.log(this.props.pins);
return <div>test</div>;
}
}
const mapStateToProps = state => ({
pins: state.pins.pins
});
export default connect(mapStateToProps, { getPins })(Pins);
the plan is to list each pin
You can add dispatch to the dependencies list, since it won't change. If you'll add the pins to dependancies list of the useEffect block, you might cause infinite loop, if the response to the action changes the state (call to server that returns an array).
However, according to the class component example, the getPins() action creator doesn't require the value of pins, so you can just add dispatch to the list of dependancies:
function MainStory() {
const pins = useSelector(state => state.pins.pins);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPins());
}, [dispatch]);
console.log(pins);
return(<div> test </div>

Separate hook from component and then import hook to component

So I finally took the deep dive into hooks. Yes, now I get how easy they can be to use. However, I know one of the most important aspects of it is reusable logic. To share the hook between components, and make my functional component(container now?) even cleaner, how would I separate this? I understand that I can create a custom hook, as long as it starts with use. So for instance, I want to fetch a bunch of tickets and get the length, I have the following:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function TicketCounter() {
const [data, setData] = useState([]);
useEffect(() => {
axios.get(`/ticketapi/ticketslist/`)
.then(res => {
if (res.data) {
setData(res.data)
}
})
}
, []);
return (
<React.Fragment>
{data.length}
</React.Fragment>
);
}
export default TicketCounter
What's the best way to do this? What method are you using? Are you storing these hooks inside your src folder? I imagine you have a folder for hooks, with each hook having it's own js file? Anyhoo, thanks in advance folks. I absolutely LOVE react and all it has to offer, and am super excited about hooks (2 months after everyone else lol).
Well I figured it out.
Sep hook file
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const useFetchAPI = (url) => {
const [data, setData] = useState([]);
useEffect(() => {
axios.get(url)
.then(res => {
if (res.data) {
setData(res.data)
}
})
}
, []);
return data;
};
export default useFetchAPI
and then my component(container?)
import React, { useState, useEffect } from 'react';
import useFetchAPI from '../hooks/TicketCounterHook'
function TicketCounter() {
const url = `/ticketapi/ticketslist/`
const data = useFetch(url)
return (
<React.Fragment>
{data.length}
</React.Fragment>
);
}
export default TicketCounter

Resources