UPDATE: There is nothing wrong with the implementation below. The error was due to a react-redux upgrade, as redux now relies on functional components instead of class components.
import React, { forwardRef } from 'react'
const DivWithRef = forwardRef((props, ref) => {
return(
<div ref={ref} {...props}>
{props.children}
</div>
)
})
class AClassComponent extends Component {
render() {
return (
<DivWithRef ref={me => this.node = me} />
)
}
}
When I try this I get the error:
Warning: Function components cannot be given refs.
Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Does react not explicitly export forwardRef?
I have been using this code for a while, and it never caused any issues. After I updated to React 16.8, I started getting the warning above, but my app can still access the DOM Ref even though react claims it shouldn't be able to.
You want to do this instead:
constructor() {
this.node = React.createRef();
}
// more code
render() {
return <DivWithRef ref={this.node} />;
}
The rest of your code is correct.
I answered it on my own. It's not a problem with my implementation. Callback refs are a valid implementation, as explained here: reactjs.org/docs/refs-and-the-dom.html#callback-refs
The reason for the error is that the latest version of react-redux uses functional components instead of class components. After I updated to react-redux, my refs on connected components return the error.
The second ref argument only exists when you define a component with React.forwardRef call.
React.forwardRef
Related
I have a React HOC that propagate an instance of a class to the children.
import React from "react";
import ObjContext from "../../context/Obj/ObjContext";
const withObj = (Component) => (props) => (
<ObjContext.Consumer>
{(obj) => <Component {...props} obj={obj} />}
</ObjContext.Consumer>
);
export default withObj;
Now, if in one of the child, I start coding, my code editor (VS Code Studio) doesn't display the properties of the object.
When I do props.obj. the editor doesn't show me all the stuff which is inside the object.
Instead, if I do const obj = new Obj() directly, I can see them.
Why is that? Is impossible to see the data which is inside the object that is propagated from a HOC and received via props?
Any workaround?
Thank you.
As said in the comments, the real answer here is TypeScript.
To see how your vs code editor would react with typescript, you can quickly do:
// Inside child component:
import Obj from 'path/to/obj'
...
export class ChildComponent extends React.Component {
this.obj: Obj;
constructor(props) {
super(props);
this.obj = this.pros.obj;
this.obj.something() // something would be proposed by ide
}
...
}
(Ofc the vs code will growl saying that types are not eligible in a .js file and you'd need .tsx instead, but for the purpose of taking a look at how TS would help with the auto-completion, its fine.
I am still learning TypeScript and declaring (advanced) types. I am trying to convert my CRA project to typescript and I have following component in it. How I should declare the types for this?
This component is almost 1:1 from react-router-dom examples, but I couldn't find any example showing this written in TypeScript
// ProtectedContent.tsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import Auth from '../services/Auth';
const ProtectedContent = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => {
if (Auth.isAuthenticated()) {
return (
<Component {...props} />
);
} else {
return (
<Redirect to={{
pathname: '/auth/signin',
state: { from: props.location },
}} />
);
}
}} />
);
};
export default ProtectedContent;
I tried creating an interface for the parameters:
interface PCProps extends HTMLAttributes<HTMLElement>{
component: React.Component;
}
const ProtectedContent = ({ component: Component, ...rest }: PCProps) => {
// .......
}
But then I was having problem with the line
return (
<Component {...props} />
);
as there was error saying
JSX element type 'Component' does not have any construct or call signatures. ts(2604)
Then when I was trying to fix that I ended going down an insane rabbit hole and lost track and understanding of what was going on anymore.
So how should I declare the types for this so that the component could be any React component and then destructure rest of the props neatly?
TS version is 3.8.3
Edit: (Possible solution)
From the depths of internet I found this "solution". I can declare the component in the PCProps interface as 'ReactType' and the use it like this:
interface PCProps {
component: React.ReactType;
}
const ProtectedContent = ({ component: Component, ...rest }): PCProps => {
...
}
This will get rid of the error, but why? What is this 'ReactType'? I went through the docs and found nothing related to it. Is there anyone experienced in React and TS who could explain this and also how this kind of component should be typed?
I don't really like to use this solution, because I have no idea what's happening.
Solution source
Edit2:
Sandbox showing original, proposed solution using React.FC and possible solution using React.ReactType. Check all the three files in this sandbox.
Sandbox for this question
Make sure your file extension is .tsx and not .ts and that you're declaring component: React.FC;. Working example: codesandbox.io/s/silly-hamilton-1xuyu?file=/src/App.tsx:95-97
I've built a ReactJS component library that I use for multiple projects installed via an NPM package using a sim link. I want to use the context API to pass data from a parent component served from the component library to my base project to be consumed by multiple consumer components also served from the component library. When I try the context is always undefined in my child components.
If I place my consumer component in my provider component within my library it works like a champ but this defeats what I'm trying to achieve. If I export both the provider and the consumer to my base project the consumer doesn't see the provider.
This is from my base project
import { Screen, COD, GenericSocketServer } from 'component-library'
export default class View extends React.PureComponent {
render() {
return (
<Screen className="screen odmb1">
<GenericSocketServer>
<COD />
</GenericSocketServer>
</Screen>
)
}
}
This is my provider code exported from my 'component-library'
import React from 'react';
import MyContext from "./context";
import COD from './../cod';
export default class GenericSocketServer extends React.Component {
render() {
return (
<MyContext.Provider value={{ foo: 'bar' }}>
<COD />
{this.props.children}
</MyContext.Provider>
);
}
}
This is my content code used in 'component-library'
import React from 'react'
const MyContext = React.createContext()
export default MyContext
This is my consumer component exported from 'component-library'
import MyContext from "../GenericSocketServer/context"
class COD extends React.Component {
render() {
return (
<React.Fragment>
<MyContext.Consumer>
{(context) => {
/*
context comes back undefined
I expect { foo: 'bar' }
*/
console.log('context :', context)
return (
<p>This should work</p>
)}}
</MyContext.Consumer>
</React.Fragment>
)
}
}
Context always comes back undefined as if it doesn't see the parent provider. I think I'm ether doing something wrong initializing the context myself or for some reason the two components I'm importing just don't share the same context. Please help!! Not sure if I should give up on this and just use redux.
Maybe you are making multiple instances of the component providing the context. Let's say you have a component Sound, which starts by:
const { Provider, Consumer } = React.createContext();
If you import this library from your main project, the context will be created at the global space. You then use it to render your document tree. But in another component you also imported this library, which had to be resolved during webpack transpilation. It thus has its own copy of the above lines and a context object created in its own space. The problem occurs when you try to use the Consumer, because the Provider was only made by the main project for the first context object, and the second context's provider instance was never instantiated, thus returns undefined.
A solution to the problem is to enforce a single context object, which you can achieve by telling the second component's webpack that the provider-owning library is an external, so when webpack reaches e.g. the "import sound" line, it will not go further and will assume this dependency is resolved at runtime. When runtime comes, it will take it from the same place where the main project is taking it. To do this in webpack, e.g. for above "sound" library, add this to your other component (not main project):
{
...
externals: {
...
'sound': 'sound'
}
...
}
Also in your component package.json:
{
...
peerDependencies: {
"sound": "^1.2.3"
}
}
Apart from Darko's answer, esm and cjs export is also a possible reason for context to fail in a package. If you use the hook in esm and the provider in cjs, you will not get the value for that context.
I recently had a similar issue where I was trying to consume the value of a context inside my library components but using the provider (imported from the package) in the host app.
I managed to solve the issue just by making react and react-dom external and peerDependencies when bundling in rollup.
should your code of consumer be
<React.Fragment>
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
</React.Fragment>
as stated from the official react doc : https://zh-hant.reactjs.org/docs/context.html
when you define
you can use it like
I'm using React 16 and need portals, but am not able to find any documentation on this feature. Does anyone know how to use this yet?
https://github.com/facebook/react/pull/10675
Thanks for any advice.
React v16 has just released a couple of hours ago (Yay!!) which officially supports Portal.
What is Portal? Since How long has it been there?
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
Portal is not new concept in the react community. Many libraries are available that supports this kind of functionality. e.g react-portal and react-gateway.
What happens when rendering any react app?
Generally, when rendering any React application, a single DOM element is used to render the whole React tree.
class HelloReact extends React.Component {
render() {
return (
<h1>Hello React</h1>
);
}
}
ReactDOM.render(<HelloReact />, document.getElementById('root'));
As you can see we are rendering our react component into a DOM element having id root.
What is Portal and why is it needed? Why is it there?
Portals are a way to render React children outside the main DOM hierarchy of the parent component without losing the react context. I'm emphasizing on it because very popular libraries like react-router, redux heavily uses the react context. So context availability when using Portal is very helpful.
As per the react docs,
A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually "break out" of its container. For example, dialogs, hovercards, and tooltip.
So, with portals, you can render a parallel react tree onto another DOM node when needed. Even though it is rendered in the different DOM node, parent component can catch the uncaught events. See this codepen provided in the docs itself.
The below example should give your more idea:
// index.html
<html>
<body>
<div id="root"></div>
<div id="another-root"></div>
</body>
</html>
// index.jsx
const mainContainer = document.getElementById('root');
const portalContainer = document.getElementById('another-root');
class HelloFromPortal extends React.Component {
render() {
return (
<h1>I am rendered through a Portal.</h1>
);
}
}
class HelloReact extends React.Component {
render() {
return (
<div>
<h1>Hello World</h1>
{ ReactDOM.createPortal(<HelloFromPortal />, portalContainer) }
</div>
);
}
}
ReactDOM.render(<HelloReact />, mainContainer);
https://codesandbox.io/s/62rvxkonnw
You can use devtools inspect element and see that <h1>I am rendered through a Portal.</h1> is rendered inside #another-root tag, whereas <h1>Hello World</h1> is rendered inside #root tag.
Hope this helps :)
Update: To answer #PhillipMunin's comment.
What is the different between ReactDOM.render and
ReactDOM.createPortal?
Even though the component rendered through the portal is rendered somewhere else (Outside the current container root), it remains present as the child of the same parent component. (Who invoked the ReactDOM.createPortal) So any events on the child are propagated to the parent. (Ofc, This doesn't work if you manually stop the propagation of the event.)
Same context is accessible inside the component rendered through a portal. But not in the case when we do ReactDOM.render directly.
I've created another demo to illustrate my point. https://codesandbox.io/s/42x771ykwx
I think we can achieve same feature by creating an external dom node outside 'root' element
node = document.createElement('div')
document.body.appendChild(node)
ReactDOM.render(<Modal {...props} />,node)
Let's say we are creating a modal view, we can create a wrapper for the same
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import Modal from './Modal'
let node = null
const ModalWrapper = (props) =>{
useEffect(()=>{
node && ReactDOM.render(<Modal {...props} />,node)
})
useEffect(()=>{
node = document.createElement('div')
document.body.appendChild(node)
ReactDOM.render(<Modal {...props} />,node)
},[])
return(
<script />
)
}
Here is an example Create Modal with React Portal and React Hooks from scratch without external library
For anyone wondering, here is how I implemented my own custom Portal component in Typescript
import React from 'react'
import ReactDOM from 'react-dom'
const Portal: React.FC<PortalProps> = (props) => {
return ReactDOM.createPortal(props.children, props.target)
}
export interface PortalProps {
target: Element;
children: React.ReactNode;
}
export default Portal
Portals are created by calling the createPortal method inside the render method of your component.
render() {
return (
<div>
{ReactDOM.createPortal(this.renderPortal(), this.props.portalEl)}
</div>
)
}
renderPortal should return contents to be rendered inside the portal, while portalEl is an external DOM element that will receive the content.
Someone recently twitted that info on portals can be found in React tests.
For example
const Popover = React.memo((props) => {
// your component implementation
return (
<div>Hi! I am a Popover in body!</div>
);
});
// This allows you to insert a component in the portal when you call the component;
export default function (props) {
return (
React.createPortal(<Popover {...props} />, document.body)
);
}
I'm trying to create a "higher-order" function in React that performs some permissions-based checks on the wrapped component and returns it accordingly.
MyComponent.js
...
export default Permissions(MyComponent)
Permissions.js
export default function Permissions(Component) {
class NewComponent extends React.Component {
// ... perform checks here
render() {
return {validPermissions && <Component />}
}
}
}
However, I'd like to be able to use this Permissions as a React Component (as opposed to a function that wraps the component export).
It would looks similar to this:
<Permissions>
<MyComponent />
</Permissions>
When I run React.Component.isPrototypeOf(Component.children) I get false in these instances. My inclination is to think that the solution is to use some React or ReactDOM method to transform the React Element into a React Component, and then perform the same checks.
How can I transform a React Element into a React Component?
Update:
I gave the bit about permissions as context, but not looking for help with regard to implementing permissions.
I am basically looking for the opposite of React.createElement(MyComponent).
You can use a functional component, which combines the best of both worlds: it's simple (just a function!) and at the same time it's a proper stateless React component.
const Permissions = ({ granted, children }) =>
granted ? React.Children.only(children) : null;
Usage:
<Permissions granted={true}>
<MyComponent />
</Permissions>