I am currently migrating my react components to react hooks, but struggle with one specific case which is accessing the mounted DOM element.
My component using React.class structure :
import { Component } from "react";
import ReactDOM from "react-dom";
class LineGraph extends Component {
componentDidMount() {
this.element = ReactDOM.findDOMNode(this).parentNode;
}
render() {
return "";
}
}
Now using react hooks, ReactDOM.findDOMNode(this) throw the following error :
TypeError: react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.findDOMNode(...) is null
Looking at ReactDOM#findDOMNode documentation, I tried to use a references on a returned empty div but after few draw element variable become null.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
export default function LineGraph () {
let myRef = React.createRef();
useEffect(() => {
element = ReactDOM.findDOMNode(myRef.current).parentNode;
}, []);
return (
<div ref={myRef}></div>
);
}
I am looking a clean way to access the DOM element in which my react code is injected/mounted.
Just for context, my goal is to inject svg content using d3 library, my code works well with components.
The problem is the timing of accessing the reference.
Try using useRef hook with a listener to myRef change, and read it when it has a valid value:
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
export default function LineGraph() {
const myRef = useRef();
useEffect(() => {
if (myRef.current) {
console.log(myRef.current.parentNode)
}
}, [])
return <div ref={myRef}></div>;
}
There are some minor issues with your code, you can make it work and simplify it at the same time.
Remove the findDomNode method
It's not required as you are already accessing a node through the ref so you can use the .parentNode property directly on the referenced DOM node.
Update Parent Node on each re-render
By removing the empty array from your useEffect method the component will access the parent node each time the parent Node updates. Thereby you always have access to the freshly updated parent node.
https://stackblitz.com/edit/react-qyydtt
export default function LineGraph () {
const parentRef = React.createRef();
let parent;
useEffect(() => {
parent = parentRef.current.parentNode
});
return (
<div ref={parentRef}></div>
);
}
Related
I am learning react and trying to use useEffect. But I am getting this error:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
To address #1 I checked the react and react-dom versions, they're the same 18.2.0.
To address #2 I don't think I am (code coming soon). I'm not sure how to check for #3, I don't think there is but then again not sure. Here is my code:
import React, { useEffect } from "react";
useEffect(() => {
document.title = `Greetings to aaaa`;
}, []);
class CustomerOnboarding extends React.Component {
render() {
return (
<div></div>
);
}
}
export default CustomerOnboarding;
Above code throws Invalid hook call error. However when I put the useEffect within the component class I get a slightly different error:
I don't know if this is related, but I am using react with ruby on rails. I have other components working just fine, without hooks that is. Any ideas?
useEffect used for functional componet. for class component you need to use
componentDidUpdate and componentDidMount
In your case :
import React from "react";
class CustomerOnboarding extends React.Component {
componentDidMount() { // this is for class component
document.title = `Greetings to aaaa`;
}
render() {
return (
<div></div>
);
}
}
export default CustomerOnboarding;
for using of useEffect you can change your Component to functional component :
import React, { useEffect } from "react";
function CustomerOnboarding() {
useEffect(()=>{
document.title = `Greetings to aaaa`;
},[]);
return (
<div></div>
);
}
export default CustomerOnboarding;
or you can convert your component to function like below
import React, { useEffect } from "react";
const CustomerOnboarding = () => {
useEffect(() => {
document.title = `Greetings to aaaa`;
}, []);
return <div></div>;
};
export default CustomerOnboarding;
So I am working a simple text adventure game using Rails/React.
I am attempting to use the following package: https://www.npmjs.com/package/typewriter-effect
The current functionality has it rendering my dialogue text for the individual storylines.
When I navigate to a new storyline (via a choice) my state updates and re render occurs. But it doesn't re render the new Typewriter effect and associated dialogue.
I have an allTypeWriters variable that creates instance for each storyline. Then I find the relative one based on the newly updated storyLineId.
When I go to the character homepage (navigate away) and then return to the storyline route it will begin the effect with the correctly associated dialogue. If I simply re render via the choice picked, the img tag associated re renders. The choices available re renders. My redux and localStorage all update. Yet the dialogue implemented by the typewriter effect itself stays the same.
I'm assuming it has something to do with the onInit function that belongs to the Typewriter class and I need to recall that? And possibly even remove the previous element after re render.
Any ideas or has anyone worked with this or a similar package in the past, thanks in advance!
import React, { useEffect } from "react";
import "../Dialogue/Dialogue.css";
// import { useEffect, useCallback } from "react";
import Typewriter from "typewriter-effect";
// import { useState } from "react";
// import { useNavigate } from "react-router-dom";
function Dialogue({ storyLine }) {
// const [localWriter, setLocalWriter] = useState({});
const allTypeWriters = JSON.parse(localStorage.getItem("stories")).map(
(story) => {
return (
<Typewriter
options={{
delay: 10,
}}
onInit={(typewriter) => {
typewriter.typeString(story.dialogue).start();
}}
/>
);
}
);
const storyLineId = JSON.parse(
localStorage.getItem("user_data")
).current_storyline;
const returnActive = () => {
const activeType = allTypeWriters.find(() => storyLineId);
return activeType;
};
useEffect(() => {
returnActive();
}, [storyLineId]);
return (
<div className="dialogue-container">
{returnActive()}
<p className="dialogue-img">{storyLine.storyline_img}</p>
</div>
);
}
export default Dialogue;
console.log('Render test') in my Test component runs twice.
The tests state is not used, I just put it there for an example. The Test component doesn't rely on the tests state but it still renders twice. Why?
index.js:
import React from "react";
import ReactDOM from "react-dom";
import AppTest from "./AppTest";
ReactDOM.render(<><AppTest /></>, document.getElementById("container"));
AppTest.js:
import Test from "./Test";
import React, {useState, useEffect} from "react";
function AppTest() {
const [tests, setTests] = useState([]);
useEffect(() => {
setTests(['test1', 'test2']);
}, []);
return (
<>
<Test />
</>
);
}
export default AppTest;
Test.js:
import React, {useEffect} from "react";
function Test() {
useEffect(() => {
console.log("Render Test");
});
return (
<h1>Test</h1>
);
}
export default Test;
You can use React.memo to prevent rerender unless props change - this has more info - How to stop re render child component when any state changed in react js?
The component Test is rendered twice because of the behaviour of the AppTest component. The useEffect function in AppTest modify the state of your component with setTests(['test1', 'test2']); so the AppTest component is re-rendered.
As previously said, you can use memoization to avoid re-render Test component:
import React, {useEffect} from "react";
const Test= React.memo(()=> {
useEffect(() => {
console.log("Render Test");
});
return (
<h1>Test</h1>
);
}
export default Test;
The useEffect runs twice because it's missing a dependency array. Add an empty array to indicate it has no dependencies, and then you will not see multiple log statements:
function Test() {
useEffect(() => {
console.log("Render Test");
}, []);
// ...
}
Let's say I have a complex App component with several children. I'd like to update some of these children (ShouldUpdate) when my variable localStorageLayouts changes, so naturally I reach for useEffect(), and add localStorageLayouts as a dependency. The problem is that useEffect causes all children to update, and I only need my ShouldUpdate component to re-render. What are some work arounds to only render the components I need updated?
import React, { useEffect, useState } from "react";
import { useStoreState, useStoreActions } from "./hooks";
export default function App() {
const localStorageLayouts = useStoreState(
(state) => state.appData.localStorageLayouts
);
const [availableLayouts, setAvailableLayouts] = useState(localStorageLayouts);
useEffect(() => {
setAvailableLayouts(localStorageLayouts);
}, [localStorageLayouts]);
return (
//...
<div>
<VeryExpensiveToRender/>
<ShouldUpdate layouts = {availableLayouts}/>
<OkNotToUpdate/>
<ShouldUpdate layouts = {availableLayouts}/>
<OkNotToUpdate/>
</div>
)
}
In an app wrapped with withApp from this:
import { withApp } from "react-pixi-fiber";
And some code that looks a little like this:
class Foo extends React.Component {
// ...
eventHandler(evt) {
console.log("Event target =", evt.target);
}
render() {
let comp = (<Sprite interactive texture={...} pointerup={eventHandler} {/* ... */} />);
console.log("Component =", comp);
return (comp);
}
}
Doing this, the object that is logged as the "Event target" is a native PIXI Sprite object, which gives me access to methods like getBounds(). I'd like to be able to access this same sort of data from the comp variable (which I would then store somewhere), but when I log that, the object I get is something different. It has a $$typeof: Symbol(react.element), so I presume it's just a React object. I'd like to find a way to get access to the PIXI object associated with it so that I can do use that object later for doing things like bounds checking on an interactive setup with various other elements.
Is there a way to do this? Or: How can I do bounds checking on interactivity into an object that isn't the current target of an event from e.g. pointerup, pointermove, etc.?
It's been a while, but if you're still looking to solve this, you need to use a ref on your Sprite component. This isn't specific to react-pix-fiber, just standard React behavior. Using ReactDOM the ref would give you access to the html element, with PIXI and react-pixi-fiber, it gives you the PIXI display object.
import React, { createRef, Component } from "react";
class Foo extends Component {
constructor() {
super();
this.ref = createRef();
}
eventHandler() {
console.log(this.ref.current);
}
render() {
return (
<Sprite
interactive
texture={...}
pointerup={this.eventHandler.bind(this)}
/>
);
}
}
Besides the event handler, this.ref will be available in other lifecycle methods. Before render though, this.ref.current will be undefined.
Alternatively, you could use function components and hooks, either the useCallback hook or a combination for useRef and useEffect.
import React, { useCallback } from "react";
const Foo = () => {
const ref = useCallback(sprite => {
console.log(sprite);
}, []);
return (
<Sprite
interactive
ref={ref}
texture={...}
pointerup={this.eventHandler.bind(this)}
/>
);
}
import React, { useEffect, useRef } from "react";
const Foo = () => {
const ref = useRef();
useEffect(() => {
console.log(ref.current);
}, []);
return (
<Sprite
interactive
ref={ref}
texture={...}
pointerup={this.eventHandler.bind(this)}
/>
);
}
I created working examples of each of these methods here (see the BunnyClass, BunnyCallback and BunnyRef files):
https://codesandbox.io/s/hardcore-maxwell-pirh3