I'm not sure if this is a known issue in Meteor. They did say that they are not supporting Modules in 1.2,but I don't know if this is a module issue or something else.
I'm attempting to structure my React components using ES6 classes, like so:
class App extends React.Component {
render() {
return <p>Hello World</p>
}
};
And then I add this route:
const {Router, Route} = ReactRouter;
Meteor.startup(function() {
React.render((
<Router>
<Route path="/" component={App} />
</Router>
), document.body)
});
This runs properly if it's all the same .jsx file. Unfortunately, it doesn't work if I split the App and Router into separate files. I get this from the browser:
Uncaught ReferenceError: App is not defined
Any ideas? It works fine in two separate files if I go back to the createClass() syntax. I tried adding an "export class", that didn't work, it returns an "Unexpected reserved word" error.
Obviously this isn't a huge problem, I can certainly use the createClass syntax, but I'm building a new app and thought I'd give it a try with the latest and greatest.
Thanks!
--Ivan
Change your first line to:
App = class App extends React.Component {
That puts 'App' into the global namespace.
Check this demo and add universe:modules to your modules. Try to use this in your app.jsx:
export default class App extends React.Component {
render() {
return <p>Hello World</p>
}
}
And in your parent:
import App from "./app"
Related
We would like to pass props to custom elements that uses createApp
// index.html
<div id="my-root">
<my-element prop1="abc"></my-element>
</div>
// my-element.vue
<script lang="ts" setup>
const props = defineProps<{ prop1: number }>();
</script>
<template>
{{props.prop1}}
</template>
This works fine, but as our custome element get bigger we would like to register components and use e.g pinia and other tools. Do use those we need to add createApp and mount it. But then prop1 is always undefined
// main.ts
import ...<lots of imports>
import AppCe from "./AppWebComponent.ce.vue";
import { createPinia } from "pinia";
// Adding code below is causing prop1 to be undefined - if we skip this part, prop1 works fine
const pinia = createPinia();
const app = createApp(App);
app.use(pinia).use(ConfirmDialog);
app.component(...<lots of components>);
app.mount("#my-root");
const ceApp = defineCustomElement(AppCe);
customElements.define("my-element", ceApp);
update:
Here's a sample without: https://stackblitz.com/edit/vue3-script-setup-with-vite-56rizn?file=src/my-element/my-element-main.js
And here's a sample with the createApp: https://stackblitz.com/edit/vue3-script-setup-with-vite-gtkbaq?file=index.html
Any idea on how we could solve this?
We have a fallback, that is to do a getElementById and read the attribute value in the mounted callback - but that is not an optimal solution.
Thanks for any ideas!
update2:
Here's an attempt using #duannex suggestion. We're getting closer, the app is availible, components registered, but still no sigar. : https://stackblitz.com/edit/vue3-script-setup-with-vite-ofwcjt?file=src/my-element/defineCustomElementWrapped.js
Based on update2 with the wrapped defineCustomElement; Just pass the props to the render function:
render() {
return h(component, this.$props)
},
https://stackblitz.com/edit/vue3-script-setup-with-vite-vfdnvg?file=src/my-element/defineCustomElementWrapped.js
I have created a bare-bones Meteor app, using React. It uses the three files shown below (and no others) in a folder called client. In the Console, the App prints out:
withTracker
rendering
withTracker
rendering
props {} {}
state null null
In other words, the App component is rendered twice. The last two lines of output indicate that neither this.props nor this.state changed between renders.
index.html
<body>
<div id="react-target"></div>
</body>
main.jsx
import React from 'react'
import { render } from 'react-dom'
import App from './App.jsx'
Meteor.startup(() => {
render(<App/>, document.getElementById('react-target'));
})
App.jsx
import React from 'react'
import { withTracker } from 'meteor/react-meteor-data'
class App extends React.Component {
render() {
console.log("rendering")
return "Rendered"
}
componentDidUpdate(prevProps, prevState) {
console.log("props", prevProps, this.props)
console.log("state", prevState, this.state)
}
}
export default withTracker(() => {
console.log("withTracker")
})(App)
If I change App.jsx to the following (removing the withTracker wrapper), then the App prints only rendering to the Console, and it only does this once.
import React from 'react'
import { withTracker } from 'meteor/react-meteor-data'
export default class App extends React.Component {
render() {
console.log("rendering")
return "Rendered"
}
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, this.props)
console.log(prevState, this.state)
}
}
What is withTracker doing that triggers this second render? Since I cannot prevent it from occurring, can I be sure that any component that uses withTracker will always render twice?
Context: In my real project, I use withTracker to read data from a MongoDB collection, but I want my component to reveal that data only after a props change triggers the component to rerender. I thought that it would be enough to set a flag after the first render, but it seems that I need to do something more complex.
This a "feature", and it's not restricted to Meteor. It's a feature of asynchronous javascript. Data coming from the database arrives after a delay, no matter how quick your server is.
Your page will render immediately, and then again when the data arrives. Your code needs to allow for that.
One way to achieve this is to use an intermediate component (which can display "Loading" until the data arrives). Let's say that you have a component called List, which is going to display your data from a mongo collection called MyThings
const Loading = (props) => {
if (props.loading) return <div>Loading...</div>
return <List {...props}></List>
}
export default withTracker((props) => {
const subsHandle = Meteor.subscribe('all.myThings')
return {
items: MyThings.find({}).fetch(),
loading: !subsHandle.ready(),
}
})(Loading)
It also means that your List component will only ever be rendered with data, so it can use the props for the initial state, and you can set the PropTypes to be isRequired
I hope that helps
Unsure if you're running into the same error I discovered, or if this is just standard React behavior that you're coming into here as suggested by other answers, but:
When running an older (0.2.x) version of react-meteor-data on the 2.0 Meteor, I was seeing two sets of distinct renders, one of which was missing crucial props and causing issues with server publications due to the missing data. Consider the following:
// ./main.js
const withSomethingCount = (C) => (props) => <C { ...props } count={ ... } />
const withPagination = (C) => (props) => <C { ...props } pagination={ ... } />
const withSomething = withTracker((props) => {
console.log('withSomething:', props);
});
// Assume we're rending a "Hello, World" component here.
export const SomeComponent = withSomethingCount(withPagination(withSomething(...)));
// Console
withSomething: { count: 0 }
withSomething: { count: 0, pagination: { ... } }
withSomething: { count: 0 }
withSomething: { count: 0, pagination: { ... } }
For whatever reason, I was seeing not only N render calls but I was seeing N render calls that were missing properties in a duplicate manner. For those reading this and wonder, there was one and only one use of the component, one and only one use of the withTracker HoC, the parent HoCs had no logic that would cause conditional passing of props.
Unfortunately, I have not discovered a root-cause of the bug. However, creating a fresh Meteor application and moving the code over was the only solution which removed the bug. An in-place update of the Meteor application (2.0 to 2.1) and dependencies DID NOT solve the issue... however a fresh installation and running a git mv client imports server did solve my problems.
I've regrettably had to chalk this up to some form of drift due to subsequent Meteor updates over the two years of development.
I've built a fairly simple React app based on create-react-app which uses the Material-UI for its interface components. It also depends on one of my own packages which also uses Material-UI (same version) for a couple of shared components.
Things were looking good locally until I ran a production build and deployed it. Some of the styles were behaving oddly, for example the Material-UI grid was much narrower than when running locally.
I did some reading and found a few instances of people discussing colliding class names under my scenario. This took me to some official Material-UI documentation which provides the following example code to use a custom class name prefix:
import JssProvider from 'react-jss/lib/JssProvider';
import { createGenerateClassName } from '#material-ui/core/styles';
const generateClassName = createGenerateClassName({
dangerouslyUseGlobalCSS: true,
productionPrefix: 'c',
});
function App() {
return (
<JssProvider generateClassName={generateClassName}>
...
</JssProvider>
);
}
export default App;
Before applying this fix when inspecting my production app's source code I could see the outermost DIV using the CSS class jss2 jss24.
After applying this fix my production app actually visually renders the same layout as my development version and so would appear to be fixed. However, examining the source shows the outermost DIV to have the class MuiGrid-container-2 MuiGrid-spacing-xs-8-24 which suggests to me something isn't right. I could leave it like this but it does mean I'm running with unoptimised code.
Am I doing something wrong here? Or is there an alternative resolution? I'm using current latest version of #material-ui/core (3.3.2) and the full contents of my App.js are:
import React, { Component } from 'react';
import { Provider } from "react-redux";
import { OidcProvider } from 'redux-oidc';
import JssProvider from 'react-jss/lib/JssProvider';
import Routes from './routes';
import store from './store';
import userManager from './utils/userManager';
import {
CustomUiTheme as Theme,
CustomUiLayout as Layout,
CustomUiSnackbar as Snackbar,
CustomUiModalAlert as Alert
} from 'custom-ui';
import Loading from './components/loading';
import { createGenerateClassName } from '#material-ui/core/styles';
const generateClassName = createGenerateClassName({
dangerouslyUseGlobalCSS: true,
productionPrefix: 'tw',
});
class App extends Component {
render() {
return (
<JssProvider generateClassName={generateClassName}>
<Provider store={store}>
<OidcProvider store={store} userManager={userManager}>
<Theme>
<Loading />
<Layout variant="xmas">
<Alert />
<Routes />
<Snackbar />
</Layout>
</Theme>
</OidcProvider>
</Provider>
</JssProvider>
);
}
}
export default App;
I have built react.js site from create-react-app.
But in production mode, there is FOUC because styles are loaded after html is rendered.
Is there any way to resolve this? I have been searching google for answers, but haven't found proper one yet.
FOUC
FOUC - so called Flash of Unstyled Content can be as very problematic as so many tries of solving this issue.
To the point
Let's consider following configuration of routing (react-router):
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
where PageLayout is a simple hoc, containing div wrapper with page-layout class and returning it's children.
Now, let's focus on the component rendering based on route. Usually you would use as component prop a React Compoment. But in our case we need to get it dynamically, to apply feature which helps us to avoid FOUC. So our code will look like this:
import asyncRoute from './asyncRoute'
const Home = asyncRoute(() => import('./Home'))
const Example = asyncRoute(() => import('./Example'))
...
<PageLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/example' component={Example} />
<Switch>
</PageLayout>
...
to clarify let's also show how asyncRoute.js module looks like:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Loader from 'components/Loader'
class AsyncImport extends Component {
static propTypes = {
load: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
}
state = {
component: null
}
toggleFoucClass () {
const root = document.getElementById('react-app')
if (root.hasClass('fouc')) {
root.removeClass('fouc')
} else {
root.addClass('fouc')
}
}
componentWillMount () {
this.toggleFoucClass()
}
componentDidMount () {
this.props.load()
.then((component) => {
setTimeout(() => this.toggleFoucClass(), 0)
this.setState(() => ({
component: component.default
}))
})
}
render () {
return this.props.children(this.state.component)
}
}
const asyncRoute = (importFunc) =>
(props) => (
<AsyncImport load={importFunc}>
{(Component) => {
return Component === null
? <Loader loading />
: <Component {...props} />
}}
</AsyncImport>
)
export default asyncRoute
hasClass, addClass, removeClass are polyfills which operates on DOM class attribute.
Loader is a custom component which shows spinner.
Why setTimeout?
Just because we need to remove fouc class in the second tick. Otherwise it would happen in the same as rendering the Component. So it won't work.
As you can see in the AsyncImport component we modify react root container by adding fouc class. So HTML for clarity:
<html lang="en">
<head></head>
<body>
<div id="react-app"></div>
</body>
</html>
and another piece of puzzle:
#react-app.fouc
.page-layout *
visibility: hidden
sass to apply when importing of specific component (ie.: Home, Example) takes place.
Why not display: none?
Because we want to have all components which rely on parent width, height or any other css rule to be properly rendered.
How it works?
The main assumption was to hide all elements until compoment gets ready to show us rendered content. First it fires asyncRoute function which shows us Loader until Component mounts and renders. In the meantime in AsyncImport we switch visibility of content by using a class fouc on react root DOM element. When everything loads, it's time to show everything up, so we remove that class.
Hope that helps!
Thanks to
This article, which idea of dynamic import has been taken (I think) from react-loadable.
Source
https://turkus.github.io/2018/06/06/fouc-react/
Greetings fellow meteorites!
I am in the process of including material ui (react based) into an existing blaze app. I'm using the meteor guide and the material-ui docs as my instructions to do this properly but unfortunately to no avail. Has anyone successfully done this before? According to the material-ui docs you are supposed to inject an MuiThemeProvider into your main App Component but I keep getting the following error:
MuiThemeProvider.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
Here is my root blaze html template:
<template name="main">
<head>...</head>
<body>
<div id="wrap">
<div id="react-app-wrapper">
{{> React component=App}}
</div>
</div>
</body>
</template>
Notice I am using https://guide.meteor.com/react.html#react-in-blaze as my guidelines and am using the meteor package react-template-helper.
Here is my main.js file:
if(Meteor.isClient){
import App from './users/client/ui/components/App.js';
Template.main.onCreated(function(){
});
Template.main.helpers({
'App' : function(){
return App;
}
}
And my App.js Component File:
import React, { Component, PropTypes } from 'react';
import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
const lightMuiTheme = getMuiTheme(lightBaseTheme);
export default class App extends Component{
constructor(props){
super(props);
}
render() {
return (
<div>
<MuiThemeProvider muiTheme={lightMuiTheme} >
</MuiThemeProvider >
</div>
);
}
}
Appreciate your help big time! I have tried everything and feeling pretty stumped right now. :( If you give the correct answer I will obviously mark it as so!
Alex
This is how MuiThemeProvider renders
render() {
return this.props.children;
}
And therefore it, React actually, complained of nothing to render since this is you use it.
render() {
return (
<div>
<MuiThemeProvider muiTheme={lightMuiTheme} >
{/* There should be something here. */}
</MuiThemeProvider >
</div>
);
}
Start to put some contents that it can serve for you.
A side notice here is that the outer <div> wrapper can be dropped on the premise that it is not of some particular use.
Good luck!