What makes a React library require preact-compat? - reactjs

I have noticed that certain libraries such as classnames are readily available in Preact but others like styled-components require preact-compat.
What makes a React library unsupported natively in preact that it requires the use of preact-compat?

Disclaimer: I work on preact.
There are several APIs in react that are not needed for preact. But because existing third-party libraries have been developed for those APIs we published preact-compat which re-implements them on top of preact.
Some examples:
Children-API:
This API is in particular interesting because it isn't needed at all for preact. With preact the children property is always an array.
// React
Children.forEach(props.children, child => ...);
Children.map(props.children, child => ...);
Children.count(props.children);
// Preact
props.children.forEach(child => ...);
props.children.map(child => ...);
props.children.length;
unmountComponentAtNode:
This is another API that is not needed for preact, because we can simply unmount any tree by rendering null:
import { render } from "preact";
import App from "./app";
// Render App into dom
render(<App />, document.getElementById("root"));
// Unmount tree
render(null, document.getElementById("root"));
If you want to drop a subtree instead of the root node you can do that via returning null from a component. Basically null is always treated as an empty value.
findDOMNode:
// React
const element = React.findDOMNode(componentInstance);
// In Preact that's just a property
const element = componentInstance.base;
In our case this even works for function components. Note that in nearly all cases refs are preferred over findDOMNode.
Summary: preact-compat contains mostly shims for third-party libraries expecting full API compatibility with react.

Related

How to lazy-load a React "widget"?

My terminology is probably wrong here, but I don't know what to call it other than a "widget" when you don't have a whole React app, but are attaching little React pieces to different roots on an otherwise static HTML page. But, that's what I'm doing:
const StripeForm = React.lazy(() => import('./Stripeform'));
// ...
const formPlaceholder = document.getElementById('cwr-stripe-form');
const formRoot = createRoot(formPlaceholder);
formRoot.render(
<React.StrictMode>
<StripeForm />
</React.StrictMode>
);
The problem I'm trying to solve is, I want to lazy-load my StripeForm, so that it doesn't load until needed (and therefor maybe never at all).
I have the React.lazy import and my webpack bundle working fine. The issue is "until needed".
As I understand it, lazy-loaded components load when they begin rendering, which I suppose is usually when they move into the viewport. And I equally suppose that "usually" is overridden by the fact that I'm calling .render, which I guess causes it to render immediately.
Those guesses might be wrong, but the fact is, when I load my web page, the supposedly lazy-loaded component is loaded, even if it's not in the viewport.
How to I get these sort of "widgets" (there are several others on the page) to load lazily (i.e., to attach to my root placeholder element, but not actually load and render until necessary)?
You're already using lazy, so React will only import the component if it's not being rendered. The problem is that you're still rendering the component by default, so the component is still being loaded once it's available.
React is declarative, so the way to solve this is to conditionally render the component only when you want it to be rendered. You can implement this by using a visibility library such as react-is-visible, for example:
import React, { useRef } from 'react'
import { useIsVisible } from 'react-is-visible'
function LazyStripeForm() {
const ref = useRef()
const isVisible = useIsVisible(ref, { once: true })
return <div ref={ref}>{isVisible && <StripeForm />}</div>
}
Now you can render LazyStripeForm instead of StripeForm and it should do what you want.
Also, if StripeForm has a lot of dependencies, you should ensure your build tool is code splitting the file so that it's not increasing the size of your main bundle unnecessarily.

`screen.getByText()` vs `getByText()` in create-react-app

I am building out tests in my React app that was built with create-react-app and I'm trying to understand what the difference is between screen.getByText() and simply getByText(). For instance, the documentation shows this kind of example:
import React from 'react';
import { render, screen } from '#testing-library/react';
import App from './App';
it('renders a title of "Dashboard"', () => {
render(<App />);
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
But I see that I can do this:
it('renders a title of "Dashboard"', () => {
const { getByText } = render(<App />);
expect(getByText('Dashboard')).toBeInTheDocument();
});
I haven't found what the difference is between using screen.getByText() vs. simply using getByText() like I've shown above.
What are the differences, and are there advantages in using one over the other in various scenarios?
It's a convenience thing. Using screen you'll find that you no longer need to constantly/explicitly have to destructure the return value of the render method to get to the selector functions (that's basically it).
The PR that introduced the feature has a clear explanation of the rationale behind it:
https://github.com/testing-library/dom-testing-library/pull/412
The only caveat is in the scenario where you provide an explicit container argument to the render method - as the screen based selector would match all elements on the document.body (where as if you destructure the render result, the selector functions will be scoped to elements within the container you provided).
If you're starting out with that library I also suggest that you take a peek into this Kent Dodds article (the same who introduced the feature and a person which is a big reference in this area), which has a bunch of goodies/best practices using testing-library with the rationale behind each recommendation:
https://kentcdodds.com/blog/common-mistakes-with-react-testing-library

How safely to pass params to react component with react-rails

I'm using react-rails to add some react componenets to an existing ruby on rails app. I just realized that all the props being passed initially to the component are easily seen if you inspect the component
<%= react_component('ProfileWeeklyWriting', {
languages: #user.languages,
currentUser: #current_user,
responses: #writing_responses,
clapLottie: asset_url('lottie/clap.json'),
clapIcon: asset_url('icons/clap.svg'),
arrowIcon: asset_url('icons/arrow_green.png')
}) %>
But when you inspect the element, allll those variables are shown!
I know I can just do an ajax call from within the component, but is there a way to pass variables to the component initially, without them being shown to the world?
Let's take a bit theory about how it works. When you do classic SPA without any backend engine you usually do something like
ReactDOM.render(<App />, document.getElementByID('root'))
Which simply render the root component on place of <div id="root" />. When you apply Rails templates on the top, you are connecting 2 different worlds. ReactDOM and Rails slim templating engine (probably) but they don't know nothing about each other. ReactRails is really simply routine which does something like this:
1 Inject custom react-rails script to page
Wait for DOM ready
Collect all [data-react-class] elements
Iterate through of them and call ReactDOM with props.
You can think of it like calling several "mini apps" but in fact those are only components (react doesn't distinguish app/component, it's always just a component)
So the code is something like this (I didn't check the original code but I wrote own react-rails implementation for my company)
function init () {
const elements = document.querySelectorAll('[data-react-class]')
if (elements.length > 0) {
Array.prototype.forEach.call(elements, node => {
if (node) {
mount(node)
}
})
}
}
function mount(node) {
const { reactClass, reactProps } = node.dataset
const props = JSON.parse(reactProps || '{}')
const child = React.createElement(window[reactClass], props)
ReactDOM.render(child, node)
}
Then the DOM ready
document.addEventListener('DOMContentLoaded', e => {
init()
})
Son in fact Rails doesn't know anything about React, and React doesn't know anything about Rails unless it's not living on window. (THIS METHOD IS HIGHLY DISCOURAGED.
In real world there are ways how to make "server" rendering, which means that this piece of code is done on server to not expose props and whole React lifecycle and just flush real prepared HTML to DOM. That means that in the lifecycle BEFORE HTML is sent to the client, there is called transpiler which compiles those components, you can read about it here
https://github.com/reactjs/react-rails#server-side-rendering
So it just calls those methods with a help of https://github.com/rails/execjs
So the only way how to "not expose" props to the client is to "pre-render" components on backend by some engine (either some JS implementation for your language or directly node.js backend). I hope I gave you a better picture how it works and how it can be solved!

How can I count the number of components on a page that have been connected with redux' connect() function?

I'm doing some performance analysis on a large React/Redux app that uses many connected components (hundreds).
I'd like to keep track of the number of connected components as a performance metric so that I can accurately estimate my per-frame budget for e.g. mapStateToProps and other Redux logic.
React devtools has access to all of the components on a page, so I'm looking to either reuse the same hook it does or would also accept any hook that allows me to enumerate all components on a page (and from there I can figure out if they're wrapped in Connect or not).
react-redux doesn't seem to have specific functionality to hook into connected components, while Redux dev tools are applied to a store as a middleware and are agnostic of connected React components.
A straightforward way is to monkey-patch react-redux module and extend connected component with specific behaviour, a demo:
import * as reactRedux from "react-redux";
let connectedCount = 0;
function patchConnect(connect) {
return (...args) => {
const wrapWithConnect = connect(...args);
return WrappedComponent => {
const Connect = wrapWithConnect(WrappedComponent);
return class ConnectWithCounter extends Connect {
componentDidMount() {
console.log(++connectedCount, this);
super.componentDidMount();
}
componentWillUnmount() {
console.log(--connectedCount);
super.componentWillUnmount();
}
};
};
};
}
reactRedux.connect = patchConnect(reactRedux.connect);
reactRedux.connectAdvanced = patchConnect(reactRedux.connectAdvanced);
// import the rest of the app that imports redux-react
import('./app').catch(console.error);
react-redux should be patched first, and modules should be writable. This puts restrictions on modular environment, because ES modules can be read-only (they are, according to the spec) and import statements may go before the rest of the code (they should, according to the spec). There's a chance that reactRedux.connect = ... patching will happen too late or won't happen at all.
For instance, the example uses Codesandbox which would hoist import './app' without an error, and the same example wouldn't work in Stackblitz because SystemJS module implementation results in read-only imports.
It's preferable to configure development environment to use CommonJS modules because require allows this kind of modifications.

3d.io api at React project

Does 3d.io support React components at future ? Now, I need to find dom element utilized "ref" from component to retrieve io3d objects.
render () {
// this.props.elements is the state from getAframeElements
if (this.props.sceneId !== '') {
this.props.elements.forEach( (item) => {
this.refs.scene.appendChild(item)
})
}
return (
<a-entity ref="scene">
</a-entity>
)
}
Do you have any guides how to use 3d.io at React project ? Or I need to use document.querySelector after componentDidMount event at React.
A few things here:
How to use react and 3d.io
Most 3dio-js methods return plain js objects and arrays that you can use with any javascript library.
Here's how you could use io3d.furniture.search in a react component: https://jsfiddle.net/wo2xpb9g/
How to combine react and a-frame
There is the aframe-react project that will make combining a-frame and react easier https://github.com/ngokevin/aframe-react
As of newer versions of react it seems possible to combine a-frame and react directly like you've done, here's an example: https://codepen.io/cassiecodes/pen/akXWjo?editors=1010
How to use io3d.scene.getAframeElements with react
getAframeElements is a convenience method which converts sceneStructure into real a-frame DOM nodes. In react projects, real DOM nodes are generated by react itself.
There are two possibilities,
Instead of getAframeElements, convert sceneStructure from io3d.scene.getStructure into react virtual DOM yourself. Hopefully a community library will be published for this soon.
Keep the a-frame DOM separate from react. This is the approach sometimes used to combine react with libraries such as D3, which directly manipulate the DOM... that approach is discussed with examples here: https://medium.com/#Elijah_Meeks/interactive-applications-with-react-d3-f76f7b3ebc71

Resources