Meteor and withTracker: why is a component rendered twice? - meteor

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.

Related

vue & vitest, data-v-[random-number] attribute being added to html

Introduction: I am working with vite and vitest, I am doing snapshot tests for components that are 100% template and do not contain any logic
Problem: data-v-[random-number] It is being added to the root element of each component and snapshots are always different
What i want: Understand why im getting this data-v-[random-number] and if possible, a way to avoid this problem
Short example code:
BaseText.vue:
<script setup lang="ts"></script>
<template>
<span><slot></slot></span>
</template>
BaseText.spec.ts:
import { describe, it, expect } from "vitest";
import { shallowMount } from "#vue/test-utils";
import BaseText from "./BaseText.vue";
describe(name, () => {
const wrapper = shallowMount(BaseText);
it("MatchSnapshot", () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
Error when runing tests:
- Expected ""<span data-v-25e5131c=""></span>""
+ Received ""<span data-v-b3462088=""></span>""

Next js and Next Auth overlapping react declarations

I am running Next js and Next Auth in multiple project, and all of a sudden all of them decided to crash with the same error.
Module parse failed: Identifier '_react' has already been declared (14:6)
File was processed with these loaders:
* ./node_modules/next/dist/build/webpack/loaders/next-swc-loader.js
You may need an additional loader to handle the result of these loaders.
| const _material = require("#mui/material");
| const _xDataGrid = require("#mui/x-data-grid");
> const _react = require("next-auth/react");
| const _reportTable = /*#__PURE__*/
a simple example that crashes looks like this...
As you can see from the example below. I am not importing react twice.
import React from "react";
import { Box } from "#mui/material";
import { DataGrid, GridColDef, GridRowsProp } from "#mui/x-data-grid";
import { getSession } from "next-auth/react";
import ReportTable from "../src/components/ReportTable";
export default function Home() {
const findSession = () => {
const session = getSession();
console.log(session);
return session;
};
return (
<Box>
<ReportTable title="Price Books">
<DataGrid
sx={{ border: "0" }}
rows={rows}
columns={columns}
headerHeight={40}
/>
</ReportTable>
</Box>
);
}
If I remove the getSession import at the top everything runs fine. The other developers on my team can run these project just fine, so I believe it's an environmental issue on my side.
Has anyone else run into this issue?
I have built the project and it works fine. The errors only occur in my dev environment.
I also cloned the repo on my personal machine and it worked fine there as well.
The problem was with a new plugin that came out today, "Code Ninja". If you are facing this issue, disable that VSCode extension.

How to conditionally render a component in Next.js without caching CSS styles?

I'm pretty new with Next.js and do not fully understand the cache functioning.
Given the following simplified example:
An index page that renders components Test1 or Test2, depending whether the current minute is even or odd:
import { Test2 } from '#src/components/test2'
import React from 'react'
const conditionallyChooseComponent = () => {
const d = new Date()
if (d.getMinutes() % 2 === 0) return <Test1 />
else return <Test2 />
}
export default function Home() {
return <div>{conditionallyChooseComponent()}</div>
}
And having the following components. Test1:
export const Test1 = () => {
const d = new Date()
return (
<div className={`${utilStyles.redContainer}`}>
<h1>It's {d.toISOString()} and I'm Test1 component. My background should be red</h1>
</div>
)
}
And Test2:
export const Test2 = () => {
const d = new Date()
return (
<div className={`${utilStyles.blueContainer}`}>
<h1>It's {d.toISOString()} and I'm Test2 component. My background should be blue</h1>
</div>
)
}
And this CSS:
.redContainer {
background-color: red;
}
.blueContainer {
background-color: blue;
}
The background color is being cached when the code is executed by building and serving from the transpiled code. When running with yarn dev it is working just fine.
Here is the unexpected result:
Screenshot with Test1 component being rendered with blue background
PS: I made this work with the workaround of using the getInitialProps to prevent Next.js from caching anything in that page but, for my real use case that option is not valid because I need the render condition to be calculated in the client side since it will depend on the local date of the browser.
Next will automatically cache all static pages that doesn't depends on external data, maybe you can implement useEffect to update your date variable or use a simple state, so it should work the way you expect
https://nextjs.org/docs/basic-features/pages#static-generation-without-data
To make that works, you will need to add some client-side code (via useEffect), so the React component updates every minute (or so). Funny enough, this is not as simple as it sounds, and even Dan Abramov has published a long post explaining why things such as setInterval may not work intuitively with React (specifically, with React Hooks).
Assuming you use the custom hook Dan explains in the article above, this should work:
export default function Home() {
const [date, setDate] = useState(new Date());
useInterval(() => {
// this will update the component's date every second
setDate(new Date());
}, 1000);
return <div>{date.getMinutes() % 2 === 0 ? <p>Test 1</p> : <p>Test 2</p>}</div>;
}
Observe that your code example only executes the conditionallyChooseComponent once, just when Next is trying to server-side rendering your page.

Store not getting passed down to connected cellrendererframework

I'm using the following components:
react v.16.5
react-redux v.6.0
ag-grid v.18.1
I'm using cellRendererFramework to display a custom component in one cell of ag-grid. However as soon as i make this custom component a connected component,
Error:
Could not find "store" in the context of "Connect(TestComponent)". Either wrap the root component in a , or pass a custom React
context provider to and the corresponding React context
consumer to Connect(TestComponent) in connect options.
the colDef in the ag-grid is as follows:
{
field: "TestField",
headerName: "Test Field",
rowGroup: false,
cellRendererFramework: TestComponent
}
// TestComponent.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class TestComponent extends Component {
render() {
return(<div>Hello</div>);
}
}
export default connect()(TestComponent);
I've created the store and defined the provider at the Index.js level.
Is it that cellrendererFrameworks cannot be connected?
I came across this issue in another stack overflow post but there they'd said this issue has been resolved in react vers. 13?
https://github.com/ag-grid/ag-grid-react/issues/88
Please note this is not for writing a testcase- i need the TestComponent to actually be connected.
Please could someone help with this as it seems a pretty fundamental bug that nested components are getting blocked from being connected.
From the docs and lilbumbleber's response:
With React 16 Portals were introduced and these are the preferred way to create React components dynamically. If you wish to try use this feature you'll need to enable it as follows:
// Grid Definition
<AgGridReact
reactNext={true}
...other bindings
If you use connect to use Redux, or if you're using a Higher Order Component to wrap the React component at all, you'll also need to ensure the grid can get access to the newly created component. To do this you need to ensure forwardRef is set:
export default connect(
(state) => {
return {
currencySymbol: state.currencySymbol,
exchangeRate: state.exchangeRate
}
},
null,
null,
{ forwardRef: true } // must be supplied for react/redux when using GridOptions.reactNext
)(PriceRenderer);
So, you could try adding those two:
Add reactNext={true} to <AgGridReact/> component
Change
connect()(TestComponent);
to
connect(null, null, null, { forwardRef: true })(TestComponent);
Edit: This bug was fixed in version 20.x

How to load data with Meteor and React?

I'm using Meteor 1.3.5 and React 15.1.0 and trying to understand the right way to load and subscribe to data from MongoDB.
Right now I'm doing this in createContainer with params, and having problems waiting for the data to be available.
Should I use states instead of props, and load the data in componentWillMount or componentWillMount? These didn't worked for me so far. I also tried to work with getMeteorData, but it isn't doing anything when the component renders.
I'm looking for a solution that will update the component when new data is coming. Thanks
import React, {Component, PropTypes} from "react";
import ReactDOM from "react-dom";
import { createContainer } from "meteor/react-meteor-data";
export default class UsersComponent extends Component{
render(){
let users = this.props.users;
console.log(users);
return (
<div>
{
(users)?
(users.map((user, i) => (
<div key={user._id}>
<p>{user.name}</p>
</div>
)))
: null
}
</div>
)
}
}
UsersComponent.propTypes = {
users: PropTypes.array.isRequired,
}
export default createContainer(({ params }) => {
return {
users: Meteor.users.find().fetch(),
};
}, UsersComponent);
Avoid using React's state to manage data with Meteor. Instead, create stateless functional components that uses only props. Read these:
Functional Components vs. Stateless Functional Components vs. Stateless Components
Stateless Functional Components in React 0.14
This makes your UI component easily reusable, regardless of how you want to laod data.
To understand how to load (reactive) data, it is useful to understand the concept/difference between presentational and container components.
Next step is to create container components using a technique of your choice that wraps/renders a UI component. Meteor guide's createContainer is the de facto approach for now. There are also other options such as Mantra (some say better but more complex).

Resources