How does arguments of React.createElement() function is executed? - javascript

const COMP = () => {
return (
<>
<h1>IN COMP</h1>
{console.log("in comp")}
</>
);
};
const App = () => {
return (
<>
<COMP />
<h1>IN APP</h1>
{console.log("in app")}
</>
);
};
I was expecting to first console "hello" then "in app". Because what BABEL compiler does to above code is:
const Comp = () => {
return /*#__PURE__*/React.createElement(/*#__PURE__*/React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "IN COMP"), console.log("in comp"));
}
const App = () => {
return /*#__PURE__*/React.createElement(/*#__PURE__*/React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "IN APP"), /*#__PURE__*/React.createElement(Comp, null), console.log("in app"));
}
Expecting output on console:
in comp
in app
But got unexpected output on console:
in app
in comp
Does react suspends execution of child components and first executes all other {js code} ?
I want to know how actually does React executes this piece of code...
What is the meaning of /#PURE/ ? Aren't children passed as in form of list then ?

React.createElement()
does not suspend the execution of child components.
The functions are executed "in one go", but recursively:
First App is executed (aka. "rendered") and creates React elements of all its child React components,
then all the created child React elements are rendered, and
then the child elements of the child elements are rendered,
... and so on.
The React.createElement() return value
Each argument (some of them) passed to React.createElement() is
itself a React element created by React.createElement().
E.g. the React.createElement() of App creates an object like:
{
"$$typeof": Symbol("react.element")
props: Object { }
type: function Comp()
name: "Comp"
...
}
You can see that Comp is still a function, i.e. it was not executed yet.
The execution order
Here I re-wrote your App() to illustrate the execution order:
const App = () => {
return React.createElement( // step 2: call `createElement()` with elements created in step 1
React.Fragment,
null,
React.createElement("h1", null, "IN APP"), // step 1: Get element to be rendered
React.createElement(Comp, null), // step 1: Get element to be rendered
console.log("in app") // step 1: console log
);
};
A simple illustration using plain javascript
This nested and recursive execution of functions might be confusing.
Here I tried to write some simple plain javascript code with an equivalent execution order:
const render = function( type ){
const element = type();
if( typeof element === 'function' ){
render( element );
} else {
console.log( 'render:', element );
}
};
const create = function( type ){
return type; // of course, React does more than this ;)
};
const child = function(){
return create( 'leaf', console.log('child') );
}
const root = function(){
return create( child, console.log('parent') );
}
render( root );
(It confuses me too, so forgive me if I made mistakes, but feel free to comment)
The /*#__PURE__*/ annotations
See:
What is the purpose of these comments in Babel output?
https://github.com/babel/babel/pull/6209

Related

React Context Value always returning undefined

I am new to working with contexts and I'm just trying to start slow. I saw a thing about logging your Provider to test the value and I am getting a constant undefined value. I have moved them right next to each other in the code to see if that changes anything.
const PromptContext = createContext('test123');
function generateRecipe() {
<PromptContext.Provider value="hello">xxx</PromptContext.Provider>
console.log(PromptContext.Provider.value);
console.log("Generating recipe...");
}
}
Upon this function being called the log value is always undefined, no matter what is put in the value of the Provider. Any ideas on fixing this?
The end goal is to get the value of the provider into this consumer which is in a separate react file
<PromptContext.Consumer>
{test => (
<h1>{test.value}</h1>
)}
</PromptContext.Consumer>
Your provider should not be part of a function (in the way you have it listed, anyway). The provider, of course, WILL be a function, but you aren't going to just be including it inside functions in the same way you showed above. It's actually easier than that!
You want it like this:
export const PromptContext = createContext();
export const PromptContextProvider = ({children}) => {
// All of your logic here
const baseValue = 'hello';
return (
<PromptContext.Provider value={{baseValue}}>
{children}
</PromptContext.Provider>
)
}
You don't mix the provider with your end function like you're doing above.
Instead, in your index.js file, you'll wrap your component:
root.render(
<PromptContextProvider>
<App />
</PromptContextProvider>
)
And then you can access your baseValue by using const {baseValue} = useContext(PromptContext) in your components.
React Context uses the component hierarchy to make state broadly available.
You create a provider with a value and use that to wrap other components. Anything in the component tree under the provider can then access the context value using either Context.Consumer or the useContext() hook.
For example
const PromptContext = createContext('test123');
const App = () => (
<PromptContext.Provider value="hello">
<ChildWithConsumer />
<ChildWithHook />
</PromptContext.Provider>
);
const ChildWithConsumer = () => (
<PromptContext.Consumer>
{(prompt) => (
<p>The context says "{prompt}"</p>
)}
</PromptContext.Consumer>
);
const ChildWithHook = () => {
const prompt = useContext(PromptContext);
return (
<p>The context says "{prompt}"</p>
);
};

How does React mount children array element of type array that are passed from parent? (from React Implementation Notes)

I recently read through the Implementation Notes provided by the official React docs which gives an overview on how the old React's stack reconciliation works under the hood. But I found some issue when I tried to understand the code, particularly in the mounting of host component part.
function mount(element) {
var type = element.type;
if (typeof type === 'function') {
// User-defined components
return mountComposite(element);
} else if (typeof type === 'string') {
// Platform-specific components
return mountHost(element);
}
}
function mountHost(element) {
...
// Mount the children
children.forEach(childElement => {
// Children may be host (e.g. <div />) or composite (e.g. <Button />).
// We will also mount them recursively:
var childNode = mount(childElement);
// This line of code is also renderer-specific.
// It would be different depending on the renderer:
node.appendChild(childNode);
});
// Return the DOM node as mount result.
// This is where the recursion ends.
return node;
}
Let's say I have the following component structure
function App() {
return (
<B>
<h1>Child 1</h1>
<h2>Child 2</h2>
</B>
)
}
function B({ children }) {
return (
<div>
<h3>Inside B</h3>
{children}
</div>
)
}
var rootEl = document.getElementById('root');
var node = mount(<App />);
rootEl.appendChild(node);
By the time we get to the mountHost(<div>) inside B, it will have the following array as children
The h3
An array of children from App, the h1 and h2
This should be the result of React.createElement of that div:
{
type: 'div',
props: {
children:[
{ type: 'h3', ... },
[{ type: 'h1, ... }, { type: 'h2', ... }]
]
}
}
In the implementation of mountHost, each element of the props.children array will be run with the mount function. But how will it handle the second element in the array in this case (the array that contains h1 and h2)? Because the mount function seems to not handle an array type.
The actual React.js framework itself already covers this case, because it works when I run those components structure above, using the framework. But I wonder how it is handled internally. Is it just an implementation details being left out from the tutorial docs? But I did check the React official repository, seems it also doesn't cover array.
So how does React actually tackle this case?

Child component's render still evaluated even though parent returns different component

I'm using swr in a react project and I'm trying to generify the loading/error messages in a parent component wrapping the components loading data.
The wrapping component is a very simple component returning different messages depending on the loadingstate.
const LoadingView = ({ loading, error, children }) => {
if (error) {
return <span>Error</span>
}
if (loading) {
return <span>Loading...</span>
}
return <Container>{children}</Container>
}
And the child component:
const WipePeriodTeams = ({ wipePeriodId }) => {
const params = useParams()
const { data, error } = useSWR(
`/some-endpoint`
)
return <LoadingView loading={!data}>{console.log(data.length)}</LoadingView> <--- ReferenceError
}
The issue being that the child component's render method is always evaluated, doesn't matter if loading is true/false which could end up in a ReferenceError due to data not loaded.
Is the return value always evaluated no matter what the parent returns? Is there a way around this?
Thanks! :)
That is the correct behaviour - the evaluation of children occurs in the parent component. You are seeing an error because data is undefined, so data.length is trying to point to a property of something that doesn't exist.
One way to avoid the error is to use && separator to check if data exists before referring to its length:
<LoadingView loading={!data}>{data && console.log(data.length)}</LoadingView>
Another approach is to replace your JSX expression with a component. I understand your example has a contrived child console.log(), but in the real world you're likely to pass in another component(s). Components are functions, so not evaluated at parent level:
const ChildComponent = ({data}) => {
return (<>{console.log(data.length)}</>)
}
const Parent = () => {
const { data, error } = useSWR(
`/some-endpoint`
);
return (
<LoadingView loading={!data}>
<ChildComponent data={data} />
</LoadingView>
);
}
Live example
There'a a few other approaches to delaying evaluation of children if you dig around online, but be cautious as some of them feel like messy workarounds.

Error when accessing incorrect property in a component that is not rendered

So I have this component that takes in a component and renders the children if the condition is met,
const Can = ({ condition, children }) => (condition ? children : null);
const obj = { a: 2 };
export default function App() {
return (
<div className="App">
<Can condition>Condition true</Can>
<Can>
{console.log('Inside false condition')}
<p>False condition {obj.b.x}</p> {/* b.x will throw error */}
</Can>
</div>
);
}
This is bound to fail, but I dont understand at what point does react parse its content?
why does the console.log execute if the condition is false?
This error doesnt occur when calling the children as a function
const Can = ({ condition, children }) => (condition ? children() : null);
const obj = { a: 2 };
export default function App() {
return (
<div className="App">
<Can>
{() => <p>False condition {obj.b.x}</p>}
</Can>
</div>
);
}
React parses its content into Javascript at compile-time. So what happens is, after the compilation, when the DOM construction begins all the components are mounted one-by-one. When, a Can component is to be mounted it's children are processed and passed as children props. During that processing, the console.log statement gets executed, and you see the output. Also, the error comes, during this processing only when obj.b.x is not found.
In the second case, when you pass children as a function, the JSX returns a function, which only gets executed when the condition is true, since the condition is false in your case, so no processing of paragraph tag and hence no error comes up.
Check the logging of children component here, you will get a greater idea.

What gets passed where and why between various constructions (const, function, arrow functions, IIFEs) in JavaScript/React

I'm trying to understand this (radically simplified for purposes of asking this question) bit of code from Wordpress:
function MyFooContainer( {
attr1,
attr2,
attr3,
someVar
} ) {
// do some stuff
return (
<>
// some components
</>
);
}
const MyFooWrapper = withDispatch( (dispatch, ownProps, registry) => ( {
// a bunch of code doing stuff, but no returns
})
)( MyFooContainer );
const MyExportedFunction = (props) => {
const { someVar } = props;
const myTest = true;
const Component = myTest
? MyFooWrapper
: SomethingElse;
return <Component { ...props } />
};
export default MyExportedFunction;
The way I understand things (which is probably poorly, as I'm new to both JS and React), someone calls MyExportedFunction with a bunch of parameters. One of those gets explicitly pulled out into someVar, by name. All of them get passed to Component as individual arguments (because of the ellipsis expansion). Component because of the way the test is set up, is just MyFooWrapper, which takes three arguments in order, so ... the first three props had better map to dispatch, ownProps, and registry? After that, I was really confused, but I guess base on this question that
const myFunction = withDispatch((args) => { })(MyFooContainer);
is an IIFE and MyFooContaner is passed as an argument to the withDispatch? But where did MyFooContainer get its arguments?
While we're here, why is MyFooContainer explicitly defined as a function, whereas the others are assigned as const? And last but not least, where does its return go? Who is catching that?

Categories

Resources