current is always null when using React.createRef - javascript

I was trying to do this.
I must be missing something, but I don't understand why current is always null in this example.
class App extends React.PureComponent {
constructor(props) {
super(props);
this.test = React.createRef();
}
render() {
return <div className="App">current value : {this.test.current + ""}</div>;
}
}
You can check my test case here

Because you forgot to assign the ref to some dom element. You are only creating it.
Write it like this:
class App extends React.PureComponent {
constructor(props) {
super(props);
this.test = React.createRef();
}
handleClick = () => alert(this.test.current.value)
render() {
return (
<div className="App">
<input ref={this.test} />
<button onClick={this.handleClick}>Get Value</button>
</div>
)
}
}
Working Example.

I know this is not the solution to OP's problem but for those who are coming from google search, one of the ways the ref.current can be null is that if the component on which the ref is being attached is a connected component. Like with react-redux connect or withRouter. For react-redux the solution is to pass forwardRef:true in the fourth option to connect.

React.createRef() is asynchronous so if you try to access the ref in componentDidMount, it will return null and later return the properties of the component in which you are referencing.
componentDidMount(): void {
if (this.viewShot && this.viewShot.current) {
this.viewShot.current.capture().then((uri) => {
console.log('do something with ', uri);
});
}
}
This is the right way to use the React.createRef() in this context.

You're missing the ref={this.test} prop.
return (
<div className="App" ref={this.test}>
current value : {this.test.current + ""}
</div>
);

This may happen in the following circumstances:
You have forgotten to pass your ref to the component i.e this.test from the question.
<Component ref={this.test} />
You are using Redux in which the component to which ref props is passed is wrapped by connect method and hence this.test.current would return null as it points to the Redux wrapper, to make this kind of component work make forwardRef: true
i.e: connect(mapStateToProps, mapDispatchToProps, null, {forwardRef: true})(Component)
If you are using withRouter and connect together then instead of one here you are having two wrappers and this.test.current would obviously return null, to overcome this make sure withRouter wraps connect
withRouter(connect(mapStateToProps, mapDispatchToProps, null, {forwardRef: true})(Component))
and then
<Component wrappedComponentRef={ref => this.test = ref} />
wrappedComponentRef is a prop used to make the wrapped component available just as forwardRef: true, you can find it in docs here

In version 17.0.2 of React, refs and it being asynchronous got changed. Code like this stopped working properly after the update:
import {useRef} from 'react';
import './kind.css'
const Kind = ({prop}) => {
// defining the ref
const kindRef = useRef('')
// print the ref to the console
console.log(kindRef)
return (
<div ref={kindRef} className="kind-container" >
<div className="kind" data-kind='drink'>Drink</div>
<div className="kind" data-kind='sweet'>Sweet</div>
<div className="kind" data-kind='lorem'>lorem</div>
<div className="kind" data-kind='ipsum'>ipsum</div>
<div className="kind" data-kind='ipsum' >food</div>
</div>
);
}
export default Kind;
After initializing the ref it takes sometime to assign it to the dom. Javascript, being a synchronous language, doesn't wait for the ref to initialize and skips straight to the log.
To fix this we will need to use useEffect like this
import { useRef, useEffect} from 'react';
import './kind.css'
const Kind = ({prop}) => {
// defining the ref
const kindRef = useRef('')
// using 'useEffect' will help us to do what ever you want to your ref varaible,
// by waiting to letting the elements to mount:'mount means after the elements get inserted in the page' and then do what you want the ref
useEffect(()=>{
// print the ref to the console
console.log(kindRef)
})
return (
<div ref={kindRef} className="kind-container" >
<div className="kind" data-kind='drink'>Drink</div>
<div className="kind" data-kind='sweet'>Sweet</div>
<div className="kind" data-kind='lorem'>lorem</div>
<div className="kind" data-kind='ipsum'>ipsum</div>
<div className="kind" data-kind='ipsum' >food</div>
</div>
);
}
export default Kind;
useEffect waits to the ref to be assigned to the DOM element and then runs the function assigned to it.

If you are using the ref in useCallback (or another hook), remember to add the ref to the dependencies:
const doSomething = useCallback(() => {
ref.current?.doIt();
}, [ref]);

<div ref={this.test}>
You have to assign ref prop to your DOM element.
In this case you have to assign ref to div element

Related

Convert class component to functional components using hooks

I am trying to convert this class component to functional component using hooks
import React, { Component, cloneElement } from 'react';
class Dialog extends Component {
constructor(props) {
super(props);
this.id = uuid();
}
render(){
return ( <div>Hello Dialog</div> );
}
}
This component is initiated with a specific ID because I may have to use multiple instances of them. How can I achieve this if I use functional component?
One solution would be to use useEffect to create your ID at the first render, and store it in the state :
const Dialog = () => {
const [id, setId] = useState(null);
useEffect(() => {
setId(uuid())
}, [])
return <div>Hello Dialog</div>
}
Giving an empty array as the second parameter of useEffect makes it unable to trigger more than once.
But another extremely simple solution could be to just... create it outside of your component :
const id = uuid();
const Dialog = () => {
return <div>Hello Dialog</div>
}
You may store it in state:
const [id] = useState(uuid()); // uuid will be called in every render but only the first one will be used for initiation
// or using lazy initial state
const [id] = useState(() => uuid()); // uuid will only be called once for initiation
You may also store it in React ref:
const id = useRef(null);
if(!id.current) {
// initialise
id.current = uuid();
}
// To access it’s value
console.log(id.current);
Any instance property becomes a ref pretty much, and you would access idRef.current in this case for the id
function Dialog() {
const idRef = useRef(uuid())
return <div>Hello Dialog</div>
}
Thank you all, your solutions works well. I also try this solution and I find it OK too: replace this.id with Dialog.id. Is there any downside with this solution?

How do i click a button in parent component and access the data in child component?

I am working with the concepts of ReactJS and stumbled upon this very interesting case,
I have a button in my parent component, which when clicked will access a simple string defined in child component.
I understand to pass data from parent to child we use, and to a child to parent we have to use callback functions, But I am not sure how do I use callback function in this scenario. I have played around a little with defining function etc but nothing seems to really work.
My Main.js file
import React from "react";
import Child from "./Child";
function handleClick(props) {
console.log("clicked");
}
function Main(props) {
return (
<div>
<button onClick={handleClick}>click</button>
{console.log(props)}
</div>
);
}
export default Main;
My Child.js component
import React from "react";
function statement() {
return "A sentence";
}
function Child(props) {
// let sentence = "This is from the child component";
return (
<div>
<p>The child says {props.name} </p>
</div>
);
}
export default Child;
Thank you for reading, sorry if it sounds too basic. Any help would be much appreciated.
you can create a ref in parent component and pass it to child component and then access the child state through that ref like:
function Main(props) {
const child = useRef()
const handleClick = () => {
// get child state
child.current.getState()
}
return <Child innerRef={child} />
}
function Child({ innerRef }) {
const [someState, setSomeState] = useState()
useImperativeRef(innerRef, () => ({ getState: () => someState }), [someState])
return <SomeComponent />
}
You can read more about above code in official docs.

React conditional style on custom click

I am trying to change a class when onClick event is fired, my function is called and it seems to be working but the class just won't update un the render, this is my code:
import React from 'react';
const Something = props => {
let opened = false;
const toggle = () => {
opened = !opened;
};
return (
<div className={opened ? 'some-class opened' : 'some-class'}>
<div className="some-inner-class" onClick={toggle}>
...content
</div>
</div>
);
};
export default Something;
I just want to be able to change style on when click event is fired.
My suggestion to you will be to use react hooks in this scenario, by that I mean, something like this, I have updated your code with my suggestion;
Hope it helps;
import React, { useState } from 'react';
const Something = props => {
// ***because this doesn't exist in a function component and we'd want to persist this value of opened between function call, a react hook in a function component will be ideal in your case.
//let opened = false;
// *suggestion as per react documentation: useState hook lets us keep local state in a function component
const [opened, setOpened] = useState(false);
// ***you don't really need this part, see my approach in second div of return
// const toggle = () => {
// opened = !opened;
// };
return (
<div className={opened ? 'some-class opened' : 'some-class'}>
<div className="some-inner-class" onClick={() => setOpened(true)}>
<p>Click me</p>
</div>
</div>
);
};
export default Something;
You need to update state in React for a rerender to occur. You should be creating state in a constructor like:
constructor() {
super();
this.state = { toggled: false };
}
Then, in your toggle function you need to call
this.setState({toggled: !this.state.toggled});
Currently your component is a stateless functional component so you would either need to rewrite it as a class that extends React.component or you could use hooks, https://reactjs.org/docs/hooks-state.html. Since it looks like you're just starting to learn how to use React I would rewrite the component as a class first and then try to refactor it using hooks (the modern practice).

Use ref in Higher Order Components

I have a Table component that I want ref to be attached to.
Use: Table.js
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
this.tableRef = React.createRef();
}
componentDidUpdate() {
//using ref
this.tableRef.current ..... //logic using ref
this.state.rows ..... //some logic
}
render() {
<TableContainer ref={this.tableRef} />
<CustomPagination />
}
}
This works fine, but now my requirement has changed, and I want to reuse the Table component with pagination applied to all the Tables in my App. I have decided to make a HOC withCustomPagination.
Use: withCustomPagination.js HOC
import CustomPagination from 'path/to/file';
const withCustomPagination = tableRef => Component => {
return class WithCustomPagination extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
}
componentDidUpdate() {
tableRef.current.state ..... //logic using ref, Error for this line
this.state.rows ..... //some logic
}
render() {
return (
<Component {...state} />
<CustomPagination />
)
}
}
}
export default withCustomPagination;
New Table.js:
import withCustomPagination from '/path/to/file';
const ref = React.createRef();
const Table = props => (
<TableContainer ref={ref} />
);
const WrappedTable = withCustomPagination(ref)(Table);
HOC withCustomPagination returns a class WithCustomPagination that has a componentDidUpdate lifecycle method that uses Table ref in the logic. So I try to pass ref created in Table.js as argument to withCustomPagination, i.e curried with ref and Table stateless component.
This use of ref is wrong and I get error: TypeError: Cannot read property 'state' of null.
I tried using Forwarding Refs, but was unable to implement it.
How do I pass the Table ref to withCustomPagination and be able to use it in HOC?
In this case you can use useImperativeHandle
It means you have to forward ref and specify which function or object or,...
you want to share with ref inside your functional component.
Here is my Hoc example :
import React from 'react';
import { View } from 'react-native';
export function CommonHoc(WrappedComponent) {
const component = class extends React.Component {
componentDidMount() {
this.refs.myComponent.showAlert();
}
render() {
return (
<>
<WrappedComponent
ref='myComponent'
{...this.state}
{...this.props}
/>
</>
);
}
};
return component;
}
and it's my stateless component
const HomeController=(props,ref)=> {
useImperativeHandle(ref, () => ({
showAlert() {
alert("called");
},
}));
return (
<Text>home</Text>
);
};
export default CommonHoc(forwardRef(HomeController));
Either restructure your code to not use a HOC for this or try using React.forwardRef:
Refs Aren’t Passed Through
While the convention for higher-order components is to pass through
all props to the wrapped component, this does not work for refs.
That’s because ref is not really a prop — like key, it’s handled
specially by React. If you add a ref to an element whose component is
the result of a HOC, the ref refers to an instance of the outermost
container component, not the wrapped component.
The solution for this problem is to use the React.forwardRef API
(introduced with React 16.3). Learn more about it in the forwarding
refs section.
via Higher-Order Components: Refs Aren’t Passed Through
In the forwarding refs section there are code examples you could use to pass refs down, but trying to yank them up will fail in your case with:
Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
In a project we took a different approach. There's an EnhancedTable component that handles all of the pagination logic and in itself has the dumb table component and the pagination component. It works pretty well but this means you would have to drill props (or use a store lib like Redux or Mobx) and add new ones that will handle pagination options. This will result in some refactoring of Table uses and you'll have to be more explicit but I would take it as a boon rather than a hindrance.
I was able to solve a simmilar issue that brought me to this thread without using forwardRef or useImperativeHandle.
By creating the ref at a higher level, and passign it down into the component and sub components that I needed to act on with the ref.
/** Parent Component has access to ref and functions that act on ref **/
import { useRef } from 'react';
const formRef = useRef(); // ref will have dom elements need accessing
const onClickFunction=()=>{ //sample function acts on ref
var inputs = formRef.current.querySelectorAll('input')
/* Act on ref here via onClick function, etc has access to dom elements
in child component and childs child components */
};
return(
<ComponentGetsAttachedRef formRef={formRef} />
//^ref sent down to component and its children
<ComponentNeedingRef onClickFunction={onClickFunction}/>
//^function with access to ref sent down to component
)
/** Child component needs to act on ref**/
export const ComponentNeedingRef = ({ onClickFunction}) =>{
return(
<button onClick={onClickFunction}>
)
}
/* Child component recieves ref and passes it down */
export const ComponentGetsAttachedRef = ({ formRef}) =>{
//ref comes in as prop gets attached to props or utilized internally
return (
<ChildsChildComponent formRef={formRef}/> //sub component passed ref down
)
}

Functions are not valid as a React child. This may happen if you return a Component instead of from render

I have written a Higher Order Component:
import React from 'react';
const NewHOC = (PassedComponent) => {
return class extends React.Component {
render(){
return (
<div>
<PassedComponent {...this.props}/>
</div>
)
}
}
}
export default NewHOC;
I am using the above in my App.js:
import React from 'react';
import Movie from './movie/Movie';
import MyHOC from './hoc/MyHOC';
import NewHOC from './hoc/NewHOC';
export default class App extends React.Component {
render() {
return (
<div>
Hello From React!!
<NewHOC>
<Movie name="Blade Runner"></Movie>
</NewHOC>
</div>
);
}
}
But, the warning I am getting is:
Warning: Functions are not valid as a React child. This may happen if
you return a Component instead of <Component /> from render. Or maybe
you meant to call this function rather than return it.
in NewHOC (created by App)
in div (created by App)
in App
The Movie.js file is:
import React from "react";
export default class Movie extends React.Component{
render() {
return <div>
Hello from Movie {this.props.name}
{this.props.children}</div>
}
}
What am I doing wrong?
I did encounter this error too because I didn't use the correct snytax at routing. This was in my App.js under the <Routes> section:
False:
<Route path="/movies/list" exact element={ MoviesList } />
Correct:
<Route path="/movies/list" exact element={ <MoviesList/> } />
So now the MoviesList is recognized as a component.
You are using it as a regular component, but it's actually a function that returns a component.
Try doing something like this:
const NewComponent = NewHOC(Movie)
And you will use it like this:
<NewComponent someProp="someValue" />
Here is a running example:
const NewHOC = (PassedComponent) => {
return class extends React.Component {
render() {
return (
<div>
<PassedComponent {...this.props} />
</div>
)
}
}
}
const Movie = ({name}) => <div>{name}</div>
const NewComponent = NewHOC(Movie);
function App() {
return (
<div>
<NewComponent name="Kill Bill" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"/>
So basically NewHOC is just a function that accepts a component and returns a new component that renders the component passed in. We usually use this pattern to enhance components and share logic or data.
You can read about HOCS in the docs and I also recommend reading about the difference between react elements and components
I wrote an article about the different ways and patterns of sharing logic in react.
In my case i forgot to add the () after the function name inside the render function of a react component
public render() {
let ctrl = (
<>
<div className="aaa">
{this.renderView}
</div>
</>
);
return ctrl;
};
private renderView() : JSX.Element {
// some html
};
Changing the render method, as it states in the error message to
<div className="aaa">
{this.renderView()}
</div>
fixed the problem
I encountered this error while following the instructions here: https://reactjs.org/docs/add-react-to-a-website.html
Here is what I had:
ReactDOM.render(Header, headerContainer);
It should be:
ReactDOM.render(React.createElement(Header), headerContainer);
I had this error too. The problem was how to call the function.
Wrong Code:
const Component = () => {
const id = ({match}) => <h2>Test1: {match.params.id}</h2>
return <h1>{id}</h1>;
};
Whereas id is a function, So:
Correct code:
return <h1>{id()}</h1>;
Adding to sagiv's answer, we should create the parent component in such a way that it can consist all children components rather than returning the child components in the way you were trying to return.
Try to intentiate the parent component and pass the props inside it so that all children can use it like below
const NewComponent = NewHOC(Movie);
Here NewHOC is the parent component and all its child are going to use movie as props.
But any way, you guyd6 have solved a problem for new react developers as this might be a problem that can come too and here is where they can find the solution for that.
I was able to resolve this by using my calling my high order component before exporting the class component. My problem was specifically using react-i18next and its withTranslation method, but here was the solution:
export default withTranslation()(Header);
And then I was able to call the class Component as originally I had hoped:
<Header someProp={someValue} />
it also happens when you call a function from jsx directly rather than in an event. like
it will show the error if you write like
<h1>{this.myFunc}<h2>
it will go if you write:
<h1 onClick={this.myFunc}>Hit Me</h1>
I was getting this from webpack lazy loading like this
import Loader from 'some-loader-component';
const WishlistPageComponent = loadable(() => import(/* webpackChunkName: 'WishlistPage' */'../components/WishlistView/WishlistPage'), {
fallback: Loader, // warning
});
render() {
return <WishlistPageComponent />;
}
// changed to this then it's suddenly fine
const WishlistPageComponent = loadable(() => import(/* webpackChunkName: 'WishlistPage' */'../components/WishlistView/WishlistPage'), {
fallback: '', // all good
});
In my case, I was transport class component from parent and use it inside as a prop var, using typescript and Formik, and run well like this:
Parent 1
import Parent2 from './../components/Parent2/parent2'
import Parent3 from './../components/Parent3/parent3'
export default class Parent1 extends React.Component {
render(){
<React.Fragment>
<Parent2 componentToFormik={Parent3} />
</React.Fragment>
}
}
Parent 2
export default class Parent2 extends React.Component{
render(){
const { componentToFormik } = this.props
return(
<Formik
render={(formikProps) => {
return(
<React.fragment>
{(new componentToFormik(formikProps)).render()}
</React.fragment>
)
}}
/>
)
}
}
What would be wrong with doing;
<div className="" key={index}>
{i.title}
</div>
[/*Use IIFE */]
{(function () {
if (child.children && child.children.length !== 0) {
let menu = createMenu(child.children);
console.log("nested menu", menu);
return menu;
}
})()}
In my case I forgot to remove this part '() =>'. Stupid ctrl+c+v mistake.
const Account = () => ({ name }) => {
So it should be like this:
const Account = ({ name }) => {
In my case
<Link key={uuid()} to="#" className="tag">
{post.department_name.toString}
</Link>
changed with
<Link key={uuid()} to="#" className="tag">
{post.department_name.toString()}
</Link>
You should use
const FunctionName = function (){
return (
`<div>
hello world
<div/>
`
)
};
if you use Es6 shorthand function it will give error use regular old javascript function.

Categories

Resources