Cannot find ref on initial render - reactjs

I just read in the official documents that componentDidUpdate isn't called on first render and I think maybe that's why dom isn't defined the first time this Component of mine is rendered.
This is a pop up modal that pops up when a page needs to be edited.
Is there any other way I can go about this?
componentDidUpdate() {
this.renderSingularForm();
}
renderSingularForm() {
let dom = ReactDOM.findDOMNode( this.refs.singularForm );
if ( this.props.pageObjToEdit && dom ) {
// DOESN'T GO HERE ON FIRST RENDER BECAUSE DOM IS NULL
createForm( window, dom, this.props.pageObjToEdit );
}
}
render() {
if ( this.props.pageObjToEdit ) {
return (
<div>
<div ref="singularForm" />
</div>
);
}
else {
return null;
}
}

This is an issue with how you are using refs. You shouldn't be using string refs anymore since they will be deprecated soon. Most people are now using an inline ref ( ref={ref => this.input = ref} ), but when you do that, the first time the component renders it will receive a null value. Then on the second render, the refs will be correctly assigned with the DOM element.
To get around this, you should supply a class method to the ref prop instead of an inline function.
Example:
This:
render() {
return (
...
<div ref="singularForm" />
...
)
}
Should be:
applyRef = ref => {
this.singularForm = ref;
}
render() {
return (
...
<div ref={this.applyRef} />
...
)
}
When you use a class method to apply a ref, it only gets called once when the actual element has been added to the dom, so you shouldn't get the initial null values anymore.
UPDATE 10/18:
You can now avoid this problem altogether using the new React.createRef to create your refs.

You can and should use the componentDidMount in order to safely get DOM elements or refs
From the DOCS:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here.
...
It can, however, be necessary for cases like modals and tooltips when
you need to measure a DOM node before rendering something that depends
on its size or position.
Also Note that you are using the old ref API.
You should use the new ref API
Running Example:
class App extends React.Component {
componentDidMount() {
console.log(this.myDiv.id);
}
render() {
return (
<div>
<div id="some-id" ref={ref => (this.myDiv = ref)}>some div</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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"></div>

Related

How to get DOM elements within (any/all) nested components in React?

I have a wrapper component in my App as follows. I have logic in the wrapper component that gets the DOM nodes with props.children that have a specific data attribute.
When the DOM elements are direct children, this works fine. When they are children of any nested component they're not found. How can I iterate through the entire structure and get all instances of the DOM nodes by attr?
I'm new to react and sure this should be straightforward, however I've not been able to implement from a number of examples / SO answers. I've tried to implement useRef and useContext hooks but can't get this?
// App.js
<Wrapper>
<div data-elem></div> // Get's found
<Component {...pageProps} />
</Wrapper>
// index.js
const Page = () => {
return (
<>
<div data-elem></div> // Not found
</>
);
}
// Wrapper.js ( simplified version )
export default function Wrapper(props) {
const detectedElements = props.children.filter((item) => item.props['data-elem'] === true);
console.log(detectedElements.length)
return (
<div className="wrapper">
{children}
</div>
)
}
Ultimately I managed to achieve this simply by using querySelectorAll within the useEffect hook. I'm not sure if this is the 'correct' way to do it, but is definitely the most straightforward of the the variations I tried, and seems to do the job, regardless of where the corresponding elements are.
useEffect(() => {
const detectedElements = document.querySelectorAll('[data-elem]')
}, []);

How come that onClick doesnt work in React in some situations?

for some weird reason the onClick doesnt work in my React App.
<GorillaSurfIn
onClick={() => {
console.log('gorilla clicked');
setShouldGorillaSurfOut(true);
}}
/>
As you can see I console.logged it, but I dont see anything in the console.
Very strange. Here is the SandCode box.
What it should do is render the GorillaSurfOut once we click on GorillaSurfIn.
Your GorillaSurfIn component never uses the props its passed. You will need to have it do something with the onClick prop, probably passing it into the div.
const GorillaSurfIn = (props) => {
return (
<div id="moving-gorilla" onClick={props.onClick}>
<motion.img
animate={{
x: 530,
y: 350,
transition: { duration: 3 }
}}
id="gorilla-surf-gif"
src={require('../images/surfing_gorilla.gif')}
/>
</div>
);
};
I think it's because GorillaSurfIn is a custom component, right ?
The onClick event only apply to DOM Elements, such as div.
To make it work, you would have to get the props inside the component, and apply it to a div inside the component
What Chandelier Axel said was write.
onClick event listener is for React elements and not for React Components. To add an event listener for a React Component you have to do something like below.
function Test({ customClickEventHandler }) {
return <div onClick={customClickEventHandler}><h1>Test Button</h1></div>;
}
class Main extends React.Component {
render() {
return (
<Test customClickEventHandler={() => console.log('Clicked')} />
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
So, you can pass a function that has to be called when clicked as props to the child component and then add an onClick listener to the top element that contains all other elements in your child component. It works as you expected. In this situation you have to pay attention to the this keyword make sure you bind the function or use an arrow function

Gathering a collection of elements for a basic tab switcher

Objective
I want to make a basic tab switcher with React, starting with gathering a collection of tab elements.
Error
The array of HTML elements I'm able to assemble after my code has run contains a number of null entries equal to that of the actual elements.
Attempt
I have a wrapper for the entire structure, and here is its render method:
render(){
return (
<section className={"TR-Resume " + this.state.closedClass} ref="resume">
<div className={"TR-ResumeInner " + this.state.collapsedClass} ref="resumeInner">
<header className="TR-ResumeHeader">
<h2>Resumé</h2>
<Link to="/" className="TR-ResumeClose" onClick={this.close.bind(this)}>×</Link>
</header>
<section className="TR-ResumeMain js-ResumeTabs" ref="resumeTabs">
<header className="TR-ResumeTabs">
<Tabs className="js-Tabs" resume={this.props.resume.resume} tabRef={el => this.tabs.push(el)} />
</header>
<section className="TR-ResumeContent">
<Sections tabs={this.props.resume.resume} sectionRef={el => this.sections.push(el)} />
</section>
</section>
</div>
</section>
)
}
You'll notice I am using the React recommended method of exposing refs of children components to parents. I am appending each to a property of the parent component: tabRef={el => this.tabs.push(el)}
I understand I am re-declaring that collection every time the block renders, so to prevent appending the same elements to the collection, I'm clearing it in componentWillUpdate() lifecycle event:
componentWillUpdate() {
this.tabs.length = 0;
}
Result
My plan is to attach click events to the tabs to switch between panels, but I can't get that far, because by the time the componentDidUpdate() event fires, this is was is output in the array when I log it to the console:
[ null, null, null, null, a.TR-ResumeTab.js-Tab.TR-ResumeTab_active, a.TR-ResumeTab.js-Tab, a.TR-ResumeTab.js-Tab, a.TR-ResumeTab.js-Tab ]
Is there a better way to do this? Am I doing something wrong?
I plan to take these to animate tab panel with GSAP and want to avoid using jQuery.
Any help would be appreciated.
This is an issue with how you are using refs. When you do an inline ref declaration ( ref={ref => this.input = ref} ), the first time the component renders, it will receive a null value. Then on the second render, the refs will be correctly assigned. So that is why you first see the 4 null values, and then the 4 correct values.
To get around this, you should supply a class method to the ref prop instead of an inline function.
Example:
This:
render() {
return (
...
<Tabs className="js-Tabs" resume={this.props.resume.resume} tabRef={el => this.tabs.push(el)} />
...
)
}
Should be:
applyRef = ref => {
this.tabs.push(ref);
}
render() {
return (
...
<Tabs className="js-Tabs" resume={this.props.resume.resume} tabRef={this.applyRef} />
...
)
}
When you use a class method to apply a ref, it only gets called once when the actual element has been added to the dom, so you shouldn't get the initial null values anymore.

onClick does not work for custom component

Why does onClick not work directly on my custom element as shown here? The function chosenResource does not get invoked.
return (
<div>
{
resources.map(resource => {
return (
<SelectListItem key={resource.id} onClick={this.chosenResource.bind(this)} resource={resource} />
)
})
}
</div>
)
However, if I encapsulate my custom element in a div element and have the onClick on the div element then it works.
return (
<div>
{
resources.map(resource => {
return (
<div key={resource.id} onClick={this.chosenResource.bind(this)}>
<SelectListItem resource={resource} />
</div>
)
})
}
</div>
)
What is wrong with the first approach?
#nrgwsth is correct, if you still want to stick with your first approach, you may use:
return (
<div>
{
resources.map(resource => {
return (
<SelectListItem key={resource.id} customClickEvent={this.chosenResource.bind(this)} resource={resource} />
)
})
}
</div>
)
Then, in your SelectListItem's render() function, use like this:
return (
<div onClick={this.props.customClickEvent}>
...
</div>
)
You can provide onClick and other events only on DOM elements.
In react, we have components(ex. SelectListItem) and elements(ex.div). Elements are the basic building blocks for the component.
Elements are transpiled into plain javascript objects which represent DOM elements where we can bind the dom events like onCLick, onHover etc.
However Component returns the instance that contains a bunch of elements but does not correspond to the DOM element.
SO if you want to bind DOM event we should do it on the react elements, not in components.
In the above code, onClick event is passed down as props to the component which can be seen by logging the props of the compoenet.

Props not updated

I'm a new user of React and I try to dispatch a modification from my redux store into my components through a container component and props.
My problem is at the end, the data isn't updated. I tested and I figured out that in a Board component, I got the correct edited state (I edit a module's name in this.state.mlodules[1].name) but this value isn't sent in the Bloc component. Here is the render function of my Board component:
render() {
const modules = this.state.modules.map((module) => (
<Draggable key={module._id} x={module.position.x} y={module.position.y} inside={this.state.inside}>
<Bloc module={module} editModule={this.props.onModuleEdited(module._id)}/>
</Draggable>
));
return (
<div className="board"
onMouseLeave={this.mouseLeave}
onMouseEnter={this.mouseEnter}>
{modules}
</div>
);
}
And here is the render function of my Bloc component (I'm using a BlueprintJS editable text):
render() {
return (
<div className="pt-card pt-elevation-3 bloc">
<span onMouseDown={this.preventDrag}>
<EditableText
className="name"
defaultValue={this.props.module.name}
onChange={this.nameChanged}
/>
</span>
</div>
);
}
Any ideas ?
Thanks !
as i mentioned in my comment, you are assigning a defaultValue and not assigning a value prop.
according to their source code on line #77 you can see that there's a value prop.
Edit: As you can see in the docs, defaultValue is uncontrolled input where's value is a controlled input
I think, issue is defaultText. defaultText will assign the default text only on initial rendering it will not update the value. So instead of that assign the props value to value property.
Like this:
value = {this.props.module.name}
Note: But it will make the field read-only, if you don't update the props value (state of parent component) in onChange method.
Check this example, when you click on text 'Click Me' it will update the state value but text of input field will not change:
class App extends React.Component{
constructor(){
super();
this.state = {a:'hello'}
}
click(){
this.setState({a: 'world'},() => {
console.log('updated value: ', this.state.a)
})
}
render(){
return(
<div>
<Child value={this.state.a}/>
<p onClick={() => this.click()}>Click Me</p>
</div>
)
}
}
class Child extends React.Component{
render(){
console.log('this.props.value', this.props.value)
return(
<input defaultValue={this.props.value}/>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
<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='app'/>

Resources