How to share components with hooks between two windows? - reactjs

I'm trying to share components between two windows. index.html has an iframe the src of which is iframe.html. I want iframe.html to render components defined in index.html. This works well, as long as components don't use any hooks. If hooks are used, Invalid hook call error occurs. Here are code for explanation. Do you have any workaround?
index.html
<body><div id="root"></div><script src="index.js"></script></body>
index.ts
import React, { FC, useState } from 'react';
import ReactDOM from 'react-dom';
const ComponentWithHook: FC = () => {
const [value] = useState('xxxx');
return <>{value}</>;
};
(window as any).getComponent = () => ComponentWithHook;
ReactDOM.render(<iframe src="iframe.html" />, document.getElementById('root'));
iframe.html
<body><div id="root"></div><script src="iframe.js"></script></body>
iframe.ts
import React, { FC } from 'react';
import ReactDOM from 'react-dom';
const Dummy: FC = () => {
const ComponentWithHook = (top as any).getComponent();
return <ComponentWithHook />;
};
ReactDOM.render(<Dummy />, document.getElementById('root'));

Self-solving:
It has resolved by passing React as a prop and using it inside a component.
index.ts
import React, { FC, useState } from 'react';
import ReactDOM from 'react-dom';
const ComponentWithHook: FC<{ React: typeof React }> = ({ React }) => {
const [value] = React.useState('xxxx');
return <>{value}</>;
};
(window as any).getComponent = () => ComponentWithHook;
ReactDOM.render(<iframe src="iframe.html" />, document.getElementById('root'));
iframe.ts
import React, { FC } from 'react';
import ReactDOM from 'react-dom';
const Dummy: FC = () => {
const ComponentWithHook = (top as any).getComponent();
return <ComponentWithHook React={React} />;
};
ReactDOM.render(<Dummy />, document.getElementById('root'));

Related

Jest and RTL: Traget Container is not a DOM element

I am trying to test my component with jest and react testing library but jest seems to think ReactDOM.render is not a DOM element. Running the test gives this error
Below is my code and the things I tried to do:
index.tsx file:
import React from 'react';
import ReactDOM from 'react-dom';
import { applyMiddleware, compose, createStore } from 'redux';
import {Provider} from 'react-redux';
import './index.css';
import thunk from 'redux-thunk';
import combinedReducer from './main/reducers/combinedReducer';
import { MyProvider } from './CustomProviders';
import reportWebVitals from './reportWebVitals';
import { App } from './main/components/App';
const devTool = process.env.NODE_ENV === 'development' && (window as any).__REDUX_DEVTOOLS_EXTENSION__ ? (window as any).__REDUX_DEVTOOLS_EXTENSION__() : (f) => f;
export const store = createStore(combinedReducer, compose(applyMiddleware(thunk), devTool));
if (process.env.NODE_ENV === 'development') {
require('css-framework.css');
}
ReactDOM.render(
<React.StrictMode>
<MyProvider>
<Provider store={store}>
<App/>
</Provider>
</MyProvider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
App.tsx file:
import { useDispatch } from 'react-redux';
import {getFeatures, setContextInStoreActn, setUser, setProduct} from '../actions/GeneralActions';
import { useContext, useEffect } from 'react';
import { MyContext } from '../MyContext';
import { BasePage } from './BasePage';
export const App = () => {
const dispatch = useDispatch();
const context = useContext(MyContext);
useEffect(() => {
dispatch(setContextInStoreActn(context));
void context.getSelectedProduct().then((selectedProduct) => dispatch(setProduct(selectedProduct)));
void context.getImpersonatingUser().then((impersonatingUser) => dispatch(setUser(impersonatingUser)));
dispatch(getFeatures());
}, []);
return (
<div>
<BasePage />
</div>
);
};
App.test.tsx file:
import { Provider } from 'react-redux';
import { render } from '#testing-library/react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { App } from '../../main/components/App';
const middlewares = [thunk];
const general = {
general: ''
};
const store = configureMockStore(middlewares)({
general
});
const mockWait = () => new Promise((resolve) => setTimeout(resolve, 550));
test('renders without crashing', () => {
render(
<Provider store={store}>
<App/>
</Provider>
);
expect(render).toHaveBeenCalledWith(<Provider store={store}> <App/> </Provider>); //this never gets executed in the test
});
I looked at a couple solutions that suggest appending a div or root element to my test render but that did not seem to do anything.
Another thing I tried to do was upgrading everything to the latest versions (latest react, react-dom, jest, RTL, etc...) and test with that but there were too many dependency issues so I abandoned that route.
Edit:
After playing around with my App.tsx file I found out that the reason it fails is because of the useEffect, removing it makes the test pass but that is not ideal.

matomo-tracker-react useMatomo returns "Invalid hook call"

im trying to implement matomo analytics within my react application (cra), however it trows an invalid hook call exception. Code has been copied from the simple example
https://www.npmjs.com/package/#datapunt/matomo-tracker-react
Did i miss something?
react: 17.0.2
react-dom: 17.0.2
code:
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";
import { MatomoProvider, createInstance } from "#datapunt/matomo-tracker-react";
const matomo = createInstance({
urlBase: "http://192.168.133.226/",
siteId: 3,
heartBeat: {
active: true,
seconds: 10,
},
linkTracking: false,
configurations: {
disableCookies: true,
setSecureCookie: true,
setRequestMethod: "POST",
},
});
ReactDOM.render(
<MatomoProvider value={matomo}>
<Provider store={store}>
<App />
</Provider>
</MatomoProvider>,
document.getElementById("root")
);
App.js
import React, { useEffect } from "react";
import "./App.scss";
import { BrowserRouter as Router, Link } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { useMatomo } from "#datapunt/matomo-tracker-react";
import Navigation from "./components/navigation/navigation";
import Sitemap from "./components/sitemap/sitemap";
import Main from "./pages/main/main";
function App() {
const modal = useSelector((state) => state.modal);
const items = useSelector((state) => state.items);
const app = useSelector((state) => state.app);
const dispatch = useDispatch();
const { trackPageView } = useMatomo();
useEffect(() => {
trackPageView();
// eslint-disable-next-line
}, []);
return (
<div className='app'>
<Router>
<Navigation />
<Sitemap />
<Main />
</Router>
</div>
);
}
export default App;
You need to create the Matamo instance first and then wrap everything around that.
Refer this https://www.npmjs.com/package/#datapunt/matomo-tracker-react
https://github.com/jonkoops/matomo-tracker/tree/main/packages/react
Use this one & try again:
npm install #jonkoops/matomo-tracker-react
import { MatomoProvider, createInstance } from '#jonkoops/matomo-tracker-react'
import { useMatomo } from '#jonkoops/matomo-tracker-react'

React HOC that takes argument instead of component and returns component

How do I create a React HOC that takes argument instead of component (let's say the argument is from useParams() of react router dom) and returns a component with the given argument (with hooks)
I tried
import React from 'react'
const ResultsContent = searchValue => {
const Content = props => {
return (
<h1>{searchValue}</h1>
)
}
return Content
}
export default ResultsContent
then
let { value } = useParams()
const Content = ResultsContent(value)
return (
<div>
<Content></Content>
</div>
)
and I got _results__WEBPACK_IMPORTED_MODULE_5___default(...) is not a function
You could do something like this:
import React from "react";
import ReactDOM from "react-dom";
const resultsContent = (searchValue) => {
const Content = (props) => <h1>{searchValue}</h1>;
return Content;
};
const Content = resultsContent("a");
const rootElement = document.getElementById("root");
ReactDOM.render(<Content />, rootElement);
**Also check if you import your ResultsContent with a default import not named import:
import ResultsContent from './your-path-to-the-file';**

React ScrollToTop component problem : it should work but it doesn't

I am coding a React app and I'm trying to use a ScrollToTop component in order to scroll down in the app. But the problem is that for some reason it is not working at all.
Please help I'm really struggling with this one...
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
}, [pathname]);
return null;
}
And I import this component in App.js
import React from 'react'
import { HashRouter } from "react-router-dom";
// import * as serviceWorker from './serviceWorker';
// core styles
import "./scss/volt.scss";
// vendor styles
import "#fortawesome/fontawesome-free/css/all.css";
import "react-datetime/css/react-datetime.css";
import HomePage from "./pages/HomePage";
import ScrollToTop from "./ScrollToTop";
function App() {
return (
<HashRouter>
<ScrollToTop/>
<HomePage />
</HashRouter>
)
}
export default App
Can anybody help me?
Please try with this.
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0,0);
}, [pathname]);
return null;
}

Merging two objects with useContext and state?

So i have this problem where it doesn't merge the two objects since refsContext is empty when useEffect gets called after render. It ends with only one of the refs in the object. useEffect(() => {setRef(something)}, [ref]); results in an infinite empty object loop. Did i miss something?
refs.context.jsx
import { createContext } from "react";
const refsContext = createContext({});
export default refsContext;
Same code over multiple files
/** #jsx jsx */
import React, { useContext, useEffect, useRef } from "react";
import refsContext from "../../context/refs.context";
const StackoverflowExample= () => {
const projectsRef = useRef(null);
const [ref, setRef] = useContext(refsContext);
useEffect(() => {
setRef({ ...ref, projects: projectsRef.current });
}, []);
return (
<section ref={projectsRef}></section>
);
};
export default StackoverflowExample;
App.jsx
import React, { useState, useEffect } from "react";
import { render } from "react-dom";
import Pages from "./Pages";
import { BrowserRouter } from "react-router-dom";
import refsContext from "./context/refs.context";
const App = () => {
//Default Contexts
const refsHook = useState({});
console.log(refsHook[0]);
//All my wrappers/providers for my App
return (
<refsContext.Provider value={refsHook}>
<BrowserRouter>
<Pages/>
</BrowserRouter>
</refsContext.Provider>
);
};
render(<App />, document.getElementById("root"));

Resources