I am currently building a noughts and crosses game with Meteor and React. When I first start the app, I create a new item in the 'Games' collection and send the id of this object to the App as a prop:
Meteor.startup(() => {
Meteor.call('games.create', function(error, result) {
var id = result;
render(<App gameId={id} />, document.getElementById('render-target'));
});
});
I then use createContainer on the App component to retrieve the Game on the Database, so I can manipulate it within the App:
export default createContainer(({gameId}) => {
Meteor.subscribe('games');
console.log(gameId);
return {
gameState: Games.find(ObjectId(gameId)).fetch(),
};
}, App);
After I added this createContainer code, the app stopped rendering. In the console, I get a very long error message, copied below. Thanks in advance!
Exception in delivering result of invoking 'games.create': require<.imports.ui["App.jsx"]</exports.default<#http://localhost:3000/app/app.js?hash=d138fc2a4d2ee444a0b4b78d568bc9e3acf71f7c:758:5
getMeteorData#http://localhost:3000/packages/react-meteor-data.js?hash=913595fb1851d69206a4cd0ce5bfbacdc56a8830:282:16
calculateData/this.computation</<#http://localhost:3000/packages/react-meteor-data.js?hash=913595fb1851d69206a4cd0ce5bfbacdc56a8830:164:22
Tracker.Computation.prototype._compute#http://localhost:3000/packages/tracker.js?hash=9f8a0cec09c662aad5a5e224447b2d4e88d011ef:339:5
Tracker.Computation#http://localhost:3000/packages/tracker.js?hash=9f8a0cec09c662aad5a5e224447b2d4e88d011ef:229:5
Tracker.autorun#http://localhost:3000/packages/tracker.js?hash=9f8a0cec09c662aad5a5e224447b2d4e88d011ef:604:11
calculateData/this.computation<#http://localhost:3000/packages/react-meteor-data.js?hash=913595fb1851d69206a4cd0ce5bfbacdc56a8830:156:16
Tracker.nonreactive#http://localhost:3000/packages/tracker.js?hash=9f8a0cec09c662aad5a5e224447b2d4e88d011ef:631:12
calculateData#http://localhost:3000/packages/react-meteor-data.js?hash=913595fb1851d69206a4cd0ce5bfbacdc56a8830:155:26
componentWillMount#http://localhost:3000/packages/react-meteor-data.js?hash=913595fb1851d69206a4cd0ce5bfbacdc56a8830:66:21
require<.node_modules["react-dom"].lib["ReactCompositeComponent.js"]</ReactCompositeComponent.performInitialMount/<#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:18931:18
measureLifeCyclePerf#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:18658:12
require<.node_modules["react-dom"].lib["ReactCompositeComponent.js"]</ReactCompositeComponent.performInitialMount#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:18930:9
require<.node_modules["react-dom"].lib["ReactCompositeComponent.js"]</ReactCompositeComponent.mountComponent#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:18841:16
require<.node_modules["react-dom"].lib["ReactReconciler.js"]</ReactReconciler.mountComponent#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:11766:18
require<.node_modules["react-dom"].lib["ReactCompositeComponent.js"]</ReactCompositeComponent.performInitialMount#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:18954:18
require<.node_modules["react-dom"].lib["ReactCompositeComponent.js"]</ReactCompositeComponent.mountComponent#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:18841:16
require<.node_modules["react-dom"].lib["ReactReconciler.js"]</ReactReconciler.mountComponent#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:11766:18
mountComponentIntoNode#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:23920:16
require<.node_modules["react-dom"].lib["Transaction.js"]</TransactionImpl.perform#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:12730:13
batchedMountComponentIntoNode#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:23942:3
require<.node_modules["react-dom"].lib["Transaction.js"]</TransactionImpl.perform#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:12730:13
require<.node_modules["react-dom"].lib["ReactDefaultBatchingStrategy.js"]</ReactDefaultBatchingStrategy.batchedUpdates#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:21609:14
batchedUpdates#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:11398:10
require<.node_modules["react-dom"].lib["ReactMount.js"]</ReactMount._renderNewRootComponent#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:24136:5
require<.node_modules["react-dom"].lib["ReactMount.js"]</ReactMount._renderSubtreeIntoContainer#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:24217:21
require<.node_modules["react-dom"].lib["ReactMount.js"]</ReactMount.render#http://localhost:3000/packages/modules.js?hash=b2852da4b705e3b46ab5b20c9b94e15e453af050:24238:12
require<.client["main.js"]</</<#http://localhost:3000/app/app.js?hash=d138fc2a4d2ee444a0b4b78d568bc9e3acf71f7c:395:3
Meteor.bindEnvironment/<#http://localhost:3000/packages/meteor.js?hash=e3f53db3be730057fed1a5f709ecd5fc7cae1229:1105:17
._maybeInvokeCallback#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:3557:7
.receiveResult#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:3577:5
._livedata_result#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:4742:7
Connection/onMessage#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:3385:7
._launchConnection/self.socket.onmessage/<#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:2736:11
_.forEach#http://localhost:3000/packages/underscore.js?hash=cde485f60699ff9aced3305f70189e39c665183c:149:7
._launchConnection/self.socket.onmessage#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:2735:9
REventTarget.prototype.dispatchEvent#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:175:9
SockJS.prototype._dispatchMessage#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:1160:5
SockJS.prototype._didMessage#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:1218:13
SockJS.websocket/that.ws.onmessage#http://localhost:3000/packages/ddp-client.js?hash=bc32a166cd269e06a394f9418e0024d805bab379:1365:9
Your problem is the ObjectId() meteor doesn't know how to handle it.
do the following:
export default createContainer(({gameId}) => {
Meteor.subscribe('games');
console.log(gameId);
return {
gameState: Games.find(new Meteor.Collection.ObjectID(gameId)).fetch(),
};
}, App);
Ideally you need to use the Subscription as a handler:
export default createContainer(({ gameId }) => {
const gamesHandle = Meteor.subscribe('games');
const isLoadingGames = !gamesHandle.ready()
const gamesState = Games.find({ _id: gameId }).fetch() // <-- make sure 'Games' is imported or avilable globally
return {
gamesState,
isLoadingGames
};
}, App);
Then you can access this.props.isLoadingGames in your <App /> component and use it to display a loading :) e.x this.props.isLoadingGames ? 'Loading...' : 'Finished...'
Related
I am trying to render a dynamically generated react component in a react app using createProtal.
When I call createProtal from a class the component is not rendered.
Handler.ts the class the contains the business logic
export class Handler {
private element: HTMLElement | null;
constructor(selector: string) {
this.element = document.getElementById(selector);
}
attachedEvent() {
this.element?.addEventListener("mouseenter", () => {
let cancel = setTimeout(() => {
if (this.element != null)
this.attachUi(this.element)
}, 1000)
this.element?.addEventListener('mouseleave', () => {
clearTimeout(cancel)
})
})
}
attachUi(domNode: HTMLElement) {
createPortal(createElement(
'h1',
{className: 'greeting'},
'Hello'
), domNode);
}
}
Main.tsx the react component that uses Handler.ts
const handler = new Handler("test_comp");
export default function Main() {
useEffect(() => {
// #ts-ignore
handler.useAddEventListeners();
});
return (
<>
<div id="test_comp">
<p>Detect Mouse</p>
</div>
</>
)
}
However when I repleace attachUi function with the function below it works
attachUi(domNode: HTMLElement) {
const root = createRoot(domNode);
root.render(createElement(
'h1',
{className: 'greeting'},
'Hello'
));
}
What am I missing?
React uses something called Virtual DOM. Only components that are included in that VDOM are displayed to the screen. A component returns something that React understands and includes to the VDOM.
createPortal(...) returns exactly the same as <SomeComponent ... />
So if you just do: const something = <SomeComponent /> and you don't use that variable anywhere, you can not display it. The same is with createPortal. const something = createPortal(...). Just use that variable somewhere if you want to display it. Add it to VDOM, let some of your components return it.
Your structure is
App
-children
-grand children
-children2
And your portal is somewhere else, that is not attached to that VDOM. You have to include it there, if you want to be displayed.
In your next example using root.render you create new VDOM. It is separated from your main one. This is why it is displayed
as the title says I can't make hellosign-embedded work with next
My app will be served though a CDN, so I don't cake if it won't work with SSR
const HelloSign: any = dynamic(
(): any => {
return import("hellosign-embedded")
},
{ ssr: false }
)
export default function Home() {
const client =
typeof window !== "undefined"
? new HelloSign({
allowCancel: false,
clientId: "HELLO SIGN CLIENT ID", // DEV HelloSign Client ID
skipDomainVerification: true,
})
: null
return null
}
I keep getting TypeError: HelloSign is not a constructor
I also created this issue on GitHub with further information
Edit:
I see now where things got confusing. hellosign-embedded references the window object (i.e., the import needs to be visible only during the client-side compilation phase). I saw that next/dynamic was being used to import a module. Since dynamic() returns a type of ComponentType and the containing client variable was inside the Home function, I had incorrectly assumed you were trying to import a React component.
It turns out hellosign-embedded isn't even a React component at all so you shouldn't use next/dynamic. You should be able to use ES2020 imports instead. Your pages/index.tsx file will look something like this:
import type { NextPage } from "next";
const openDocument = async () => {
// ES2020 dynamic import
const HelloSign = (await import("hellosign-embedded")).default;
const client = new HelloSign({
allowCancel: false,
clientId: "HELLO SIGN CLIENT ID", // DEV HelloSign Client ID
skipDomainVerification: true,
});
client.open("https://www.example.com");
};
const Home: NextPage = () => {
return <button onClick={() => openDocument()}>Open</button>;
};
export default Home;
disregard old answer:
HelloSign is of type ComponentType, so you can use the JSX element stored in the variable directly:
// disregard: see edit above
// export default function Home() {
//
// const client =
// typeof window !== "undefined" ? (
// <HelloSign
// allowCancel={false}
// clientId="HELLO SIGN CLIENT ID" // DEV HelloSign Client ID
// skipDomainVerification={true}
// />
// ) : null;
//
// return client;
//
// };
I am testing a react web app where I can display reports from Power BI. I am using powerbi-client-react to embed the reports. However, I face an issue when I load the component with an expired token, I get this error: Content not available screenshot.
So whenever that happens, I catch it with the error event handler, get a new token and update the powerbi report accessToken. However, it doesn't seem to reload/refresh the embed when I set the new accessToken in react. It only displays the screenshot above.
Error log screenshot.
Is there a way to force refresh the embed component with the new access token? or is my approach not correct? Any mistakes pointer would be appreciated.
import React from 'react';
import {models} from 'powerbi-client';
import {PowerBIEmbed} from 'powerbi-client-react';
// Bootstrap config
let embedConfigTest = {
type: 'report', // Supported types: report, dashboard, tile, visual and qna
id: reportId,
embedUrl: powerBIEmbedURL,
accessToken: null,
tokenType: models.TokenType.Embed,
pageView: 'fitToWidth',
settings: {
panes: {
filters: {
expanded: false,
visible: false,
},
},
background: models.BackgroundType.Transparent,
},
};
const PowerBiReport = ({graphName, ...props}) => {
let [embedToken, setEmbedToken] = React.useState();
let [embedConfig, setEmbedConfig] = React.useState(embedConfigTest);
React.useEffect(
() => {
setEmbedToken(EXPIRED_TOKEN);
setEmbedConfig({
...embedConfig,
accessToken: EXPIRED_TOKEN, // Initiate with known expired token
});
},
[graphName]
);
const changeSettings = (newToken) => {
setEmbedConfig({
...embedConfig,
accessToken: newToken,
});
};
// Map of event handlers to be applied to the embedding report
const eventHandlersMap = new Map([
[
'loaded',
function() {
console.log('Report has loaded');
},
],
[
'rendered',
function() {
console.log('Report has rendered');
},
],
[
'error',
async function(event, embed) {
if (event) {
console.error(event.detail);
console.log(embed);
// Simulate getting a new token and update
setEmbedToken(NEW_TOKEN);
changeSettings(NEW_TOKEN);
}
},
],
]);
return (
<PowerBIEmbed
embedConfig={embedConfig}
eventHandlers={eventHandlersMap}
cssClassName={'report-style-class'}
/>
);
};
export default PowerBiReport;
Thanks #vtCode. Here is a sample but the refresh can only happen in 15 secs interval.
import { PowerBIEmbed } from "powerbi-client-react";
export default function PowerBiContainer({ embeddedToken }) {
const [report, setReport] = useState(null);
useEffect(() => {
if (report == null) return;
report.refresh();
}, [report, embeddedToken]);
return (
<PowerBIEmbed
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
getEmbeddedComponent={(embeddedReport) => setReport(embeddedReport)};
/>
);
}
Alternatively, you can add the React "key" attribute which remounts the component when embededToken changes
<PowerBIEmbed key={embeddedToken}
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
/>
I ended up solving this issue, although not so beautiful.
I checked the powerbi-client wiki as it has dependency on it and found out that you could use embed.reload() in the embed object I get from the error function.
For some reason (I could not find out why), the error handler gets triggered twice, so to avoid refreshing the token twice, I had to create a dialog notifying the user that the token had expired and whenever that dialog is closed, I reload the powerbi report.
Exact wiki reference:
Overriding Error Experience
Reload a report
Update embed token
I have a NextJS application and I want to add Google auto translate widget to my app.
So made a function like this:
function googleTranslateElementInit() {
if (!window['google']) {
console.log('script added');
var script = document.createElement('SCRIPT');
script.src =
'//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit';
document.getElementsByTagName('HEAD')[0].appendChild(script);
}
setTimeout(() => {
console.log('translation loaded');
new window.google.translate.TranslateElement(
{
pageLanguage: 'tr',
includedLanguages: 'ar,en,es,jv,ko,pt,ru,zh-CN,tr',
//layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
//autoDisplay: false,
},
'google_translate_element'
);
}, 500);
}
And I call this function in useEffect(), it loads but when I route to another page it disappers.
When I checked the console I saw translation loaded so setTimeout scope called every time even when I route to another page but translation widget is not appear, only appear when I refresh the page.
How can I solve this?
Thanks to the SILENT's answer: Google no longer support this widget.
So I'm going to configure next-i18next which is a i18n (lightweight translation module with dynamic json storage) for NextJS.
Also, I think the problem with this widget was Google's JS code is attach that widget to DOM itself so it's not attached to VirtualDOM, thats why when I route in app, React checked VirtualDOM and update DOM itself so the widget disappear because it's not on VirtualDOM. (That's just a guess)
Edit: after further testing I found that this code might still be unstable. Be careful if using it in production.
Use the code below inside your custom app and do not forget to put <div id="google_translate_element" /> inside your page or component. Based on this and this answers.
import { useEffect } from 'react'
import { useRouter } from 'next/router'
const MyApp = ({ Component, pageProps }) => {
const { isFallback, events } = useRouter()
const googleTranslateElementInit = () => {
new window.google.translate.TranslateElement({ pageLanguage: 'en' }, 'google_translate_element')
}
useEffect(() => {
const id = 'google-translate-script'
const addScript = () => {
const s = document.createElement('script')
s.setAttribute('src', '//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit')
s.setAttribute('id', id)
const q = document.getElementById(id)
if (!q) {
document.body.appendChild(s)
window.googleTranslateElementInit = googleTranslateElementInit
}
}
const removeScript = () => {
const q = document.getElementById(id)
if (q) q.remove()
const w = document.getElementById('google_translate_element')
if (w) w.innerHTML = ''
}
isFallback || addScript()
events.on('routeChangeStart', removeScript)
events.on('routeChangeComplete', addScript)
return () => {
events.off('routeChangeStart', removeScript)
events.off('routeChangeComplete', addScript)
}
}, [])
return <Component {...pageProps} />
}
export default MyApp
Before initializing a React App, I fetch the user's info in the localStorage. Then, thanks to his id, I start a socket connection with the server.
When passing the user's id to the socket function, typescript claims that:
Argument of type 'Promise' is not assignable to parameter of type 'string'
Here is my index.tsx file:
const user = fetchUser();
if (user) {
getNotifications(user);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
And the fetchUser() function:
export async function fetchUser() {
try {
if (!localStorage.getItem("MyAppName")) return null;
const user = await jwt_decode(localStorage.MyAppName);
updateUser(user);
return user.id;
} catch (err) {
return null;
}
}
How to fix this? If I remove the async/await it works, but performance-wise, is it ok to block the thread like this before initializing the app?
getUser is returning a promise as any function defined with async, which you are manipulating prior to its resolution. Because you're operating in a module global scope, you could do as follows, which would execute in non-blocking manner:
getUser.then(user => {
if(user) {
getNotifications(user);
}
})
.catch(e => console.log('Error: ', e))
I'm assuming that your App takes the notifications as a prop in some way. We want to load the notifications asynchronously and allow the App to render. Once the fetch is finished, the props for the App will change to include the notifications. Depending on what your app is expecting, you can either pass an empty array of notifications, or you can also add some prop which serves as a flag, like isLoadingNotifications, so that you can know to render a spinner within the app instead of the notifications.
This all should be done within a react component. Probably further down the tree, but for now lets just wrap your App inside an OuterApp.
interface Notification {
/**... */
}
const OuterApp = () => {
const [notifications, setNotifications] = useState<Notification[]>([]);
const loadNotifications = async () => {
await fetchUser();
if ( user ) {
const notes = await getNotifications( user );
setNotifications( notes );
}
}
useEffect( () => {
loadNotifications();
// should probably have a cleanup
}, [] );
return (
<App
notifications={notifications}
/>
)
}
ReactDOM.render(
<OuterApp />,
document.getElementById("root")
);