What actually happens when React component returns? - javascript

I have noticed a difference between the data before returning and after a return of a component.
class AComponent extends Component {
render() {
const body = <BComponent crmStatus={...}/>
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = <article className='large'>...</article>
debugger // log resultRender on the left
return resultRender
}
}
My former question was going to be "How to read rendered component's className?", but I have split the questions as answering what is actually happening and why is it like that really started to bug me and might even give me hints to solve my problem.
So the question is:
What is actually happening to the component and why is it like that? I can have really complicated logic in my render() function, but I guess working with the components isn't that easy.
const headerContact = isContactInCRM ? <p>..</p> : <div>..</div>
const headerCallBtnsOrInfo = isSipEnabled && <div>..buttons..</div>
const callTimer = callDuration && <span>{callDuration}</span>
const footerNotes = <footer>..</footer>
const someImportedComponent = <MyComponent />
const resultRender = <section>
{headerContact}
{headerCallBtnsOrInfo}
{callTimer}
{footerNotes}
{someImportedComponent}
</section>
// there is a difference in data between headerContact and someImportedComponent
// when traversing the resultRender's tree in console

Before answering the question, it's worth to look at what is JSX. It just provides syntactic sugar for the React.createElement(component, props, ...children) function.
<div>
<MyComponent/>
</div>
As an example, above JSX snippet will be transformed to following JavaScript code in the compilation process.
React.createElement(
"div",
null,
React.createElement(MyComponent, null)
);
You can try out this using Babel online repl tool. So if we rewrite your example code using normal JavaScript (after compiling JSX), it will be something like this.
class AComponent extends Component {
render() {
const body = React.createElement(BComponent, { crmStatus: '...' });
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = React.createElement('article',{ className: 'large' }, '...' );
debugger // log resultRender on the left
return resultRender
}
}
By looking at above code, we can understand that <BComponent crmStatus={...}/> doesn't create a new object of BComponent class or call render method of BComponent. It just create a ReactElement with BComponent type and crmStatus prop. So what is a ReactElement? ReactElement is a pain JavaScript object with some properties. I recommend you to read this post from official React blog to get an in-depth understanding of React components, elements, and instances.
An element is a plain object describing a component instance or DOM node and its desired properties. It contains only information about
the component type (for example, a Button), its properties (for
example, its color), and any child elements inside it.
Basically, what you have printed in the console is two React elements in different types. The left one is describing DOM node with type 'article' and the right one is describing BComponent type React component instance. So simply you can't expect them to be the same.
Then where does React create an instance of BComponent? Actually, this happens internally in the React code. Usually, we don't have access to these instances or what return by their render methods in our application code.
However, React still provide an escape hatch called 'refs' which you can explicitly access instances of child components. You might be able to use that approach to solve your original problem.
Hope this helps!

Related

Vue child component not displaying dynamic data on first page load

Given the code below, my child component alerts trigger before any of the code in the Parent mounted function.
As a result it appears the child has already finished initialization before the data is ready and therefor won't display the data until it is reloaded.
The data itself comes back fine from the API as the raw JSON displays inside the v-card in the layout.
My question is how can I make sure the data requested in the Parent is ready BEFORE the child component loads? Anything I have found focuses on static data passed in using props, but it seems this completely fails when the data must be fetched first.
Inside the mounted() of the Parent I have this code which is retrieves the data.
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray).then(() => {
console.log('DATA ...') // fires after the log in Notes component
this.checkAttendanceForPreviousTwoWeeks().then(()=>{
this.getCurrentParticipants().then((results) => {
this.currentP = results
this.notesArr = this.notes // see getter below
})
The getter that retrieves the data in the parent
get notes() {
const newNotes = eventsModule.getNotes
return newNotes
}
My component in the parent template:
<v-card light elevation="">
{{ notes }} // Raw JSON displays correctly here
// Passing the dynamic data to the component via prop
<Notes v-if="notes.length" :notesArr="notes"/>
</v-card>
The Child component:
...
// Pickingn up prop passed to child
#Prop({ type: Array, required: true })
notesArr!: object[]
constructor()
{
super();
alert(`Notes : ${this.notesArr}`) // nothing here
this.getNotes(this.notesArr)
}
async getNotes(eventNotes){
// THIS ALERT FIRES BEFORE PROMISES IN PARENT ARE COMPLETED
alert(`Notes.getNotes CALL.. ${eventNotes}`) // eventNotes = undefined
this.eventChanges = await eventNotes.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
...
I am relatively new to Vue so forgive me if I am overlooking something basic. I have been trying to fix it for a couple of days now and can't figure it out so any help is much appreciated!
If you are new to Vue, I really recommend reading the entire documentation of it and the tools you are using - vue-class-component (which is Vue plugin adding API for declaring Vue components as classes)
Caveats of Class Component - Always use lifecycle hooks instead of constructor
So instead of using constructor() you should move your code to created() lifecycle hook
This should be enough to fix your code in this case BUT only because the usage of the Notes component is guarded by v-if="notes.length" in the Parent - the component will get created only after notes is not empty array
This is not enough in many cases!
created() lifecycle hook (and data() function/hook) is executed only once for each component. The code inside is one time initialization. So when/if parent component changes the content of notesArr prop (sometimes in the future), the eventChanges will not get updated. Even if you know that parent will never update the prop, note that for performance reasons Vue tend to reuse existing component instances when possible when rendering lists with v-for or switching between components of the same type with v-if/v-else - instead of destroying existing and creating new components, Vue just updates the props. App suddenly looks broken for no reason...
This is a mistake many unexperienced users do. You can find many questions here on SO like "my component is not reactive" or "how to force my component re-render" with many answers suggesting using :key hack or using a watcher ....which sometimes work but is almost always much more complicated then the right solution
Right solution is to write your components (if you can - sometimes it is not possible) as pure components (article is for React but the principles still apply). Very important tool for achieving this in Vue are computed propeties
So instead of introducing eventChanges data property (which might or might not be reactive - this is not clear from your code), you should make it computed property which is using notesArr prop directly:
get eventChanges() {
return this.notesArr.map(note => {
return {
eventInfo: {
name: note.name,
group: note.groupNo || null,
date: note.displayDate,
},
note: note.noteToPresenter
}
})
}
Now whenever notesArr prop is changed by the parent, eventChanges is updated and the component will re-render
Notes:
You are overusing async. Your getNotes function does not execute any asynchronous code so just remove it.
also do not mix async and then - it is confusing
Either:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray)
await this.checkAttendanceForPreviousTwoWeeks()
const results = await this.getCurrentParticipants()
this.currentP = results
this.notesArr = this.notes
or:
const promisesArray = [this.loadPrivate(),this.loadPublic()]
Promise.all(promisesArray)
.then(() => this.checkAttendanceForPreviousTwoWeeks())
.then(() => this.getCurrentParticipants())
.then((results) => {
this.currentP = results
this.notesArr = this.notes
})
Great learning resource

How does React.createRef() actually work?

I went through React docs for React Refs, and this what it says about createRef()
createRef() receives the underlying DOM element as its current property. When the ref attribute is used on a custom class component, the ref object receives the mounted instance of the component as its current .
I have some questions on this. First have look at below component.
import React, { Component } from "react";
class ImageCard extends Component {
constructor(props) {
super(props);
this.imageRef = React.createRef();
console.log("Called in constructor", this.imageRef);
}
componentDidMount() {
console.log("Called when component did mount ", this.imageRef);
}
render() {
const { description, urls } = this.props.image;
return (
<div>
<img ref={this.imageRef} src={urls.regular} alt={description} />
</div>
);
}
}
export default ImageCard;
So, in the constructor I created a React Ref, and assigned to a property called imageRef. And, in the render() method, I passed that React Ref to img React element as an attribute as a ref.
What does React Ref does here?
img will eventually become an object which has a property called ref with value this.imageRef, how it receive img as it's current property?
If it was something like this this.imageRef.current = img(object), It can be possibly. But I don't understand the above way i.e ref={this.imageRef}
Also, For both console statements this is the output that I get.
So, in the constructor current property is null that is valid. But, when I expand it, It has all properties that of img printed in componentDidMount i.e also clinetHeght How?
I don't know, If there is short explanation for this or someone have to right a full page. If that's too big to answer, external links or references would be helpful.
I also am not interested in nitty-gritty of library implementation, just an overview would be helpful so that I can use React.createRef() with confidence or without any doubt.
For how ref gets assigned, you have to remember that JSX compiled down to plain old javascript. A very simplified example of what is going on under the covers is something like this:
function createRef(initialValue) {
return {
current: initialValue
}
}
const root = document.getElementById('root');
function render(elementType, innerText, ref) {
const el = document.createElement(elementType);
if (ref) {
ref.current = el;
}
el.innerText = innerText;
root.replaceChildren(el);
}
const ref = createRef(null);
console.log(ref);
render('div', 'Hello World', ref);
console.log(ref);
<div id="root"></div>
So basically - when you use <img ref={this.imageRef} src={urls.regular} alt={description} />, the ref is passed as a property, and in the function that renders it, it assigns the actual DOM node to ref.current.
So, to answer your first question, React will assign the DOM element to the current property of the ref. In your case, that means that this.imageRef.current will be a reference to the image element as soon as the component has rendered.
The console output portion makes it a bit confusing, but it is not due to some magic done by React, but rather it is due to how the browser console handles objects whose properties change after the object has been logged. For example, if you run the following code in your console and then expand the output, you will see the same behavior your are seeing with the ref.
const obj = { current: null }
console.log(obj)
obj.current = 'something'
Here is a screenshot of that what that looks like.

Can I put react component inside state?

Can't find any recent official info if any of the three options below is allowed?
constructor(props) {
this.state = {
item: <SomeItem />,
item1: () => <SomeItem />,
item2: SomeItem,
};
}
I found this answer but it references an old link from web archive which says:
What Shouldn’t Go in State?
...
React components: Build them in render()
based on underlying props and state.
But that link doesn't say why that is a bad idea, if it will introduce bugs, etc.
This is a really good question.
The reason that putting components in state is advised against is just that it goes fundamentally against the React model, which is that a component provides a render method (which is a pure function) that the React engine uses to automatically update the DOM to reflect the values of the component's props and state.
The output of that render, i.e. the React Element, is supposed to be used directly by the React engine. The contract is that your app, and all its components, generate a bunch of Elements in a pure way for the React engine to manage.
By doing things like introducing side effects in render, or putting the Elements in state, you're essentially breaking the 'pure' contract and it may give unpredictable results, which may or may not be considered bugs in your application. The specifics of the bugs may even change with different versions of React, with different engine implementations. The point is that you're breaking the React contract, so whilst it may work in some cases, it also may not in others or even the same cases as React itself changes. The behaviour is not guaranteed.
React has built-in ways to cache renders based on prop values, like React.memo, that the engine provides and understands, and are part of the contract. If you want to cache render output for performance reasons, this is the way to do it.
Indeed, this is exactly why such functions are provided by the React API rather than just letting you do it yourself.
At the end of the day, React component instances are just objects, and you can store objects in state, so it shouldn't cause any trouble if you avoid pitfalls. One such pitfall is that if you're creating handlers to put on their props, those handlers will close over the context in which they're created, which may lead to some unexpected outcomes. Here's an example of that:
const {useState, Fragment} = React;
function Thingy({onClick}) {
return <div onClick={onClick}>A</div>;
}
// STALE CLOSURE
function Example() {
const [value, setValue] = useState(0);
const [comp, setComp] = useState(
<Thingy onClick={() => { console.log("A: value = " + value); }} />
);
const handler = () => {
setValue(v => {
++v;
console.log("B: value = " + v);
return v;
});
};
return <Fragment>
{comp}
<div onClick={handler}>B</div>
</Fragment>;
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
This is the classic stale closure thing. It's probably a bit easier to do accidentally using functional components and hooks (as I did there) rather than class components, but it's definitely possible to do with class components as well.
But if you're not doing that (either not creating functions for the component you're storing, or creating ones that don't use anything they close over that may change), it should be fine.
But look at React.memo, which may be a better answer depending on what your reason for wanting to put component instances in state is.
You can do something like this, if I understand you right
const Title = () => {
return <h1>Hello CodeSandbox</h1>;
};
class App extends React.Component {
state = {}
constructor(props) {
super(props)
this.state = {
item: function() {
return <Title />;
}
};
}
render() {
return (
<div className="App">
{this.state.item()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
export default App;
You can do it, but it's a bit strange. A React element is an object like any other. In this case, it will be the result of a method call:
// `<SomeItem/>` compiles to
React.createElement(SomeItem, null);
// A similar `React.createElement("div", null)` becomes
const el = {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: "div",
_owner: null,
}
It's strange because it's unnecessary (and a little confusing). You can just generate the element (including any state or props updates) whenever you need it.
There's also a risk that you break one of the core guarantees of React: elements are immutable. Storing the element like this gives you a chance to mutate it and thus confuse React.
If you need many copies of the same element then it may be slightly more performant to keep it like this, especially if it is expensive to generate.

React Hooks -- Uncaught Invariant Violation: Objects are not valid as a React child

I'm working on the freeCodeCamp drum machine app. In my app with function arrow components, I set state of display with the useState hook in the parent component and pass it as a prop to the child component. In the parent component, I try to render the display state in a div. However, when the method is triggered (on click of the "drum pad" div), the app crashes. In the console I get an error that says "Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {display}). If you meant to render a collection of children, use an array instead."
I've been following along a YouTube tutorial for this project but using arrow function components and Hooks instead of regular classes as used in the tutorial--in the tutorial (around 1:55 of this video) the person successfully does what I'm trying to do, so I think the issue is something to do with using Hooks or arrow function components.
// APP COMPONENT (PARENT)
const sounds = [
{ id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3' },
// etc.
];
const App = () => {
const [display, setDisplay] = useState(''); // <----
const handleDisplay = display => { // <----
setDisplay({ display });
}
return (
<div className="App">
<div className="drum-machine">
<div className="display">
<p>{display}</p> // <---- Related to error in console
</div>
<div className="drum-pads">
{sounds.map(sound => (
<DrumPad
id={sound.id}
letter={sound.letter}
src={sound.src}
handleDisplay={handleDisplay} // <----
/>
))}
</div>
</div>
</div>
);
}
// DRUMPAD COMPONENT (CHILD)
const DrumPad = ({ id, letter, src, handleDisplay }) => {
let audio = React.createRef();
const handleClick = () => {
audio.current.play();
audio.current.currentTime = 0;
handleDisplay(id); // <----
}
return (
<div
className="drum-pad"
id={id}
onClick={handleClick}
>
<p className="letter">{letter}</p>
<audio
ref={audio}
id={letter}
src={src}
>
</audio>
</div>
);
}
You're setting the state as an object instead of a string. Remove the curly brackets around it.
const handleDisplay = display => {
setDisplay(display);
}
This was already answered, but since you are following a tutorial, I am assuming you are learning React and wanted to point a couple of things to help you :)
The incorrect use of state was pointed out, but just for clarification (and the reason I think you were using an object): in the "old" way, with Class components, the state used to be an object, and you needed to update it like an object. This example here shows that. With Hooks, you don't need to set the whole State object, only that specific state property. More info here.
Another point is, in your CodePen example at least, you were missing the import for useState. You either need to import it like this import { useState } from React or use it like this React.useState, since this is a separate module, not imported by default when you import React.
The last point is, when creating components using a loop (like your <DrumPad> with the map) you need to provide a "key" attribute. that will help React keep track of things that needs to be updated or rerendered.
O updated your code with those changes in this link, if you wanna see it working:
https://codesandbox.io/s/reverent-browser-zkum2
Good luck and hope you are enjoying React Hooks :)

React - proper state management for rows of unmounted JSX?

We have a crazy DOM hierarchy, and we've been passing JSX in props rather than embedding children. We want the base class to manage which documents of children are shown, and which children are docked or affixed to the top of their associated document's window.
List (crazy physics writes inline styles to base class wrappers)
Custom Form (passes rows of JSX to Base class)
Base Class (connects to list)
Custom Form (passes rows of JSX to base class)
Base class (connects to list)
The problem is that we're passing deeply nested JSX, and state management / accessing refs in the form is a nightmare.
I don't want to re-declare every row each time, because those rows have additional state attached to them in the Base Class, and the Base Class needs to know which rows actually changed. This is pretty easy if I don't redeclare the rows.
I don't know how to actually deal with rows of JSX in Custom Form.
Refs can only be appended in a subroutine of render(). What if CustomForm wants to measure a JSX element or write inline CSS? How could that JSX element exist in CustomForm.state, but also have a ref? I could cloneElement and keep a virtual DOM (with refs) inside of CustomForm, or depend on the base class to feed the deeply-nested, mounted ref back.
I believe it's bad practice to write component state from existing state. If CustomForm state changes, and I want to change which rows are passed to BaseClass, I have to throttle with shouldComponentUpdate, re-declare that stage document (maintaining row object references), then call setState on the overarching collection. this.state.stages.content[3].jsx is the only thing that changed, but I have to iterate through every row in every stage document in BaseClass when it sees that props.stages changed.
Is there some trick to dealing with collections of JSX? Am I doing something wrong? This all seems overly-complicated, and I would rather not worsen the problem by following some anti-pattern.
Custom Form:
render () {
return <BaseClass stages={this.stages()}/>
}
stages () {
if (!this._stages) this._stages = { title: this.title(), content: this.content() };
return this._stages;
}
title () {
return [{
canBeDocked: false,
jsx: (
<div>A title document row</div>
)
}
}
content () {
return [{
canBeDocked: false,
jsx: (
<div>Hello World</div>
)
}, {
canBeDocked: true,
jsx: (
<div>Yay</div>
)
}
}
What I usually do is just connect the lower level components via Redux. This helps with not passing the state in huge chunks from the top-most component.
A great video course by one of the React creators, Dan Abramov: Getting started with Redux
Absolutely agree with #t1gor. The answer for us was to use REDUX. It changed the entire game for us. Suddenly a button that is nested 10 levels deep (that is, inside a main view, header, header-container, left side grid, etc, etc, deeper and deeper) into purely custom components, has a chance to grab state whenever it needs.
Instead of...
Parent (pass down state) - owns state vars
Child (will pass down again) - parent has state vars
Grandchild (will pass down a third time) - grandparent has state vars
Great Grandchild (needs that state var) - great grandparent has state vars
You can do...
Parent (no passing) - reads global state vars
Child
Grandchild
Great Grandchild - also reads same global level state vars without being passed...
Usually the code looks something like this...
'use strict'
//Importation of Connection Tools & View
import { connect } from 'react-redux';
import AppView from './AppView';
//Mapping -----------------------------------
const mapStateToProps = state => {
return {
someStateVar: state.something.capturedInState,
};
}
const mapDispatchToProps = dispatch => {
return {
customFunctionsYouCreate: () => {
//do something!
//In your view component, access this by calling this.props.customFunctionsYouCreate
},
};
}
//Send Mappings to View...
export default connect(mapStateToProps, mapDispatchToProps)(AppView);
Long story short, you can keep all global app state level items in something called a store and whenever even the tiniest component needs something from app state, it can get it as the view is being built instead of passing.
The issue is having content as follows, and for some reason not being able to effectively persist the child instances that haven't changed (without re-writing the entire templateForChild).
constructor (props) {
super(props);
// --- can't include refs --->
// --- not subroutine of render --->
this.state = {
templateForChild: [
<SomeComponentInstance className='hello' />,
<AnotherComponentInstance className='world' />,
],
};
}
componentDidMount () {
this.setState({
templateForChild: [ <div className='sometimes' /> ],
}); // no refs for additional managing in this class
}
render () {
return ( <OtherManagerComponent content={this.state.templateForChild} /> );
}
I believe the answer could be to include a ref callback function, rather than a string, as mentioned by Dan Abramov, though I'm not yet sure if React does still throw a warning. This would ensure that both CustomForm and BaseClass are assigned the same ref instance (when props.ref callback is executed)
The answer is to probably use a key or createFragment. An unrelated article that addresses a re-mounting problem. Not sure if the fragment still includes the same instances, but the article does read that way. This is likely a purpose of key, as opposed to ref, which is for finding a DOM node (albeit findDOMNode(ref) if !(ref instanceof HTMLElement).

Categories