https://codesandbox.io/s/cocky-silence-486sh?file=/src/App.js
In the above example, we see that calling the ChildWrapper as ChildWrapper() vs <ChildWrapper/> causes some differences.
The function method behaves as intended, so when I increment the counter, the reference stays the same, however using the component call, it actually remounts.
I was wondering if my intuition is correct, that the references are changing, and more important why are they changing. And to add on to that, which should be the preferred method for rendering functions that returns jsx, <Component/> or { Component() }.
When you do <ChildWrapper/> it is translated to React.createElement(ChildWrapper, {}, null) which means you ask react to create an element for you.
When you do ChildWrapper() you are just calling a function, react does not know about this and cannot know about it, so you won't have any of react's features (hooks).
With your example, with logging the two results in the console you will see this, and you can notice that the difference is noticeable:
When calling the function, it will return a <div>, here, jsx will transpiles it to an object with type div.
When calling it via jsx from the start, it will return an object with type ChildWrapper.
PS: you are creating the ChildWrapper inside a component (during render) So it will have a new value each time your Parent component renders, and react after rendering and going into reconcialiation, it will remove the whole previous tree and create a new one, because the type changed.
Let's sum up all what I've wrote:
The child "behaving correctly for you" is the function call, because you basically call it and it returns a div, react after render and during reconciliation, finds a div and then just updates it if there is a change. So the mount useEffect(() => ..., []) will not be executed.
Why the child created with jsx isn't behaving correclty ? because you are dynamically creating its type, so at each render, it will create a new ChildWrapper Type, and react during reconciliation will remove it entirely because its type changed, and thus, the mount useEffect(() => ..., []) will be executed each time, because every time we mount a new element.
Read more about reconciliation in this link
From the official documentation :
Each JSX element is just syntactic sugar for calling
React.createElement(component, props, ...children). So, anything you
can do with JSX can also be done with just plain JavaScript.
Internally, the code generated looks like this: React.createElement(Component, { props }, children),
The createElement function will then register and render the component you gave to it, bind it with the shadow DOM and update it whenever it is necessary. It will also pass its props and children arguments.
But if you call your component directly, none of that happens, your component will not be correctly rendered and updated by react because it lacks the core features given by createElement.
Related
I'm trying to do an ajax call when a state changes, and then set another state to the result of that ajax call.
const [plc, setPlc] = useState(null);
const [tags, setTags] = useState([]);
...
useEffect(()=>{
if(plc != null) {
myAjaxPromiseFunction(plc).catch((err)=>{
console.log(err);
}).then((tags)=>{
setTags(tags);
});
}
}, [plc]);
For some reason, this results in an infinite loop. However, when I remove the setTags(tags); statement it works as expected. It appears that the setTags function is causing the effect hook to update, but tags is not a part of the dependencies of this effect hook.
FYI, the tags variable is supposed to be a list of objects.
EDIT:
I have one other useEffect in my code.
useEffect(() => { // Update which tags are logged
if(plc != null) {
anotherAjaxFunction(plc, tags).catch(err => {
toaster.show({
message: err,
intent: "danger"
});
}).then(tags => {
setTags(tags);
});
}
}, [plc, updateLoggedFlag]);
However, this useEffect is not dependant on tags, and the issue still occurs if I remove this block of code.
Side note: The updateLoggedFlag is a variable I was planning to use to force the effect to update as needed, but I haven't used it anywhere yet.
Another EDIT: Link to reproduction using code sandbox: https://codesandbox.io/s/cranky-ives-3xyrq?file=/src/App.js
Any ideas? Thanks for the help.
In short: move TagColumn outside of App, as it is here.
The problem is not connected to the effects declaration, it's due to the component declaration.
In your sandbox, the TagColumn was defined inside of App. It means that every time App was rendered (in particular, every time its state is changed), the const TagColumn was assigned a different value. React can't see that it is the same component every time - after all, it's not really the same, since the captured scope is changing.
So, App renders a new TagColumn. React sees that component is created from scratch, renders it, and, in particular, invokes its hooks, including every useEffect. Inside useEffect, you use callback to change the state of App. App rerenders, creates yet another component TagColumn, which is again rendered for the first time in its life, calling every useEffect... and the cycle continues.
In general, you shouldn't capture anything non-constant in the functional component scope. Instead:
if the value is generated by external context (App, in this case) - pass it as props;
if the value is generated by the component itself - use useState, useRef or other hooks, as necessary.
The problem I think with your code is that it does not include tags in the dependency array. Conceptually, this should work fine, as mentioned before by other developers, but React documentation states that, all the state variables and props used inside the callback, should be present inside the dependency array, otherwise it can cause bugs. Have a look at it here. Hooks FAQ
I'm trying to wrap my head around the data flow in a React app w/ functional components & hooks.
I'm wondering:
When a data change (state change) causes a cascade of code to execute... what code (say, in each component, does and does not run... apparently there is selectivity such is "don't put that variable in the deps array if you don't want that code to run")?
How is the "family" part of the family tree determined during such a data-cascade? Does data pass to siblings? Does it only go to a child (or a parent if a function was passed down for updating the parent)?
To clarify what I have in mind, I have ended each file name with a labeling convention like so: I claim that (and request corrections!) 1 is the parent of 2; 2 is the parent of 3a (I think... can a custom hook be a "child"?), 3b, and 3c; and 3c is the parent of 4c.
Clearly parent/child data flow is a natural part of React. What about sibling to sibling? Is that where problems happen? Certainly "passing data" within a given file can be dangerous (vis-à-vis having control over if and when rendering of a piece of data happens) and apparently the solution is to "lift" the data up the tree. But even still... there is no sense in lifting data up a level (or more) if it isn't clear how it trickles back down... and what problems we should be looking our for.
index1.tsx
...
<App/>
...
App2.tsx
...
const App = () => {
...
const {varFromCustomHook} = useAppLogic(varToCustomHook);
...
<FooComponent varToFoo={varToFoo} functToFoo={functToFoo}/>;
<BarComponent/>;
...
};
...
useAppLogic3a.tsx
...
interface Props {
varToCustomHook;
};
const useAppLogic (props: Props) {
...
return {varFromCustomHook};
};
FooComponent3b.tsx
...
interface Props {
varToFoo;
functToFoo;
}
const FooComponent = (props: Props) => {
...
funcToFoo(importantData);
...
<div>{varToFoo}</div>;
...
};
BarComponent3c.tsx
...
const BarComponent = () => {
...
<FoobarComponent/>;
...
};
FoobarComponent4c.tsx
...
const FoobarComponent = () => {
...
};
A react component is a react component, whether it is a class-based component or a functional component is an implementation detail. Data flows down the react tree, parent to child, in the form of props. This is a universal truth in React.
When a data change (state change) causes a cascade of code to
execute... what code (say, in each component, does and does not run...
apparently there is selectivity such is "don't put that variable in
the deps array if you don't want that code to run")?
When state and/or props update, the functional component is rerendered. The entire function body of a functional component is technically the "render" function, so all of it is run when the component renders.
You ask specifically about hooks. Hooks are also executed each render cycle, in the order they are declared, and if there exists a dependency array, it is evaluated and if any dependency fails shallow reference equality check then the hook's callback is triggered.
How is the "family" part of the family tree determined during such a
data-cascade? Does data pass to siblings? Does it only go to a child
(or a parent if a function was passed down for updating the parent)?
The React tree is determined the same way it has almost always been determined, a root node, and children, where each child component can have further children. Data is still passed only from Parent to Child. Callbacks are still passed as props (generally) for a child component to invoke.
Comment Questions
Is useAppLogic considered a child [of App in this case], or can custom
hooks not be considered children (for whatever reason)? Assuming the
answer is yes, then couldn't useAppLogic return a value that gets
passed to its sibling, FooComponent? If yes, wouldn't this be data
flowing "horizontally" and not down? I don't know the answer... but it
seems like this kind of data-pass is possible (maybe it is an
anti-pattern, I don't know).
No, useAppLogic is a react hook and can't be a child of anything, it's a function. Only react components, HTML elements, and primitives (string, number, etc.) can be a child, rendered as JSX. Data flows only down. If data needs to be passed to siblings it needs to be lifted to at least the nearest common ancestor. If useAppLogic is in App, and FooComponent is a child of App, then any value returned by the hook can be passed as a prop to FooComponent.
What if (in the above case we have been discussing in these comments)
useAppLogic was use by both App and App's child, FooComponent? Would
this be an anti-pattern? This would apparently allow a parent and a
child to have a piece of data that was not "passed down". (To go out
on a limb... is this a window into a conversation on global
data/useReducer?). Maybe these points here in the comments would help
some people if they were in the answer.
React hooks are each their own instance. They don't share any state, or anything else for that matter. There isn't enough context to say whether or not both parent and child component using the same react hook is an anti-pattern, but I'm inclined to say no, it isn't, since any functional component can use any react hook for just about any reason. Not a window into any global data (useContext hook would be about as close as you could get to some "global" data).
Must be some fundamental wrong-headedness with my understanding. I have a react native app that is supposed to display multiple modal popups which I have implemented as a stack of render functions.
render() {
if (this.state.popupStack.length > 0) {
return this.state.popupStack[0]()
} else {
return null
}
}
For instance to display something on the stack, I do this:
this.state.popupStack.push( () => <SomeComponent thing={propA} /> )
this.state.popupStack.push( () => <SomeComponent thing={propB} /> )
And it works fine, I can push all sorts of things onto the stack and shift them out and the next thing renders.
The issue occurs if the components (not functional elements) need state which I initialize in their constructors. The first time I use SomeComponent in this way, the constructor is called and all is good. But if I push the same component again, the component is rendered according to the changed props, but the constructor isn't called. React figures the component is mounted and is happy to pass new props in, but it messes things up because it is using the previous components state. The 2nd render function pushed on the stack is using the same instance of SomeComponent. I dont want that.
Does this makes sense?
I figured it would call the constructor every time...
What's my malfunction :)
Thanks very much
Constructor would be called only once. Here is the lifecycle cheatsheet for react. Only render and other lifecyle hooks are called with each update. You may want to use props instead of local state if you want SomeComponent to always have updated values from parent component
The clue was that React knows these are different instances
render() {
<SomeComponent/>
<SomeComponent/>
}
because even though they are the same type of component, there are clearly two of them.
but react doesnt know if there is only a single render function called that has the same Component type returned by it. As far as its concerned, there is only one component and it is the same type of component each time, so its the same component.
The way React keeps tack of these things is with 'key'. The following code does what I want (instantiates a different component each time)
this.state.popupStack.push( () => <SomeComponent key = '1' thing={propA} /> )
this.state.popupStack.push( () => <SomeComponent key = '2' thing={propB} /> )
The usual nature in mobile apps unlink webapps, when pages/screens are loaded once on view they are pushed to the stack and maintained in memory for back navigation, forward navigations etc. So there is no need to invoke the constructor to re-initialize the memory for the component. The best way is to either use getDerivedStateFromProps, but it executes on all screens, even if the screen is not visible on the view port.
It will be flexible if you check the current modal is in the viewport, and handle the action, like below post. component-viewport
As I'm only starting to fully understand, om.core/build and om.next's factory functions return React element objects, which refer to component functions/classes, and the actual component is only instantiated later by React's reconciler. That is, (om.core/build some-component data) doesn't actually call some-component immediately.
However, we often represent simple, "stateless" components as just functions which take props and return a React element. In the (pure) React world, you'd use one of these functions like a component class, as React.createElement(AStatelessComponent, {some: "props"}), or more conveniently in JSX as <AStatelessComponent some="props" />. Those too return a React element which references AStatelessComponent, which won't actually be called until later.
But in Om, when we have a simple component like this (and by "we" I mean me and my team, at least), we call the function directly. Thus,
(render [this]
(om/div {}
(a-stateless-component {:some "data"})))
Here, a-stateless-component is called immediately, and whatever it returns is inserted directly into the div, rather than being substituted later by the React reconciler.
Is there a preferred way to React.createElement in Om? Or is it preferred to just call functions like this directly, even though it skips creating a component instance in the render tree?
In Om, if you want to instantiate a stateless component you need to call js/React.createElement directly.
Why you would want to do that depends:
if you call React.createElement you get a "tracked" instance in React's reconciler
if you don't, you get inlining but the stateless component now cannot be distinguished from its parent in React's render tree.
EDIT: I just realized that om.next/factory is permissive enough that it allows you to instantiate the stateless components you were talking about. So you can achieve what you want both by calling js/React.createElement directly on a function of props, or by calling om.next/factory with that same function as argument.
Here's a working example. The following code:
((om/factory #(dom/div nil "Hello, World")))
results in the following component (in React's devtools):
Suppose I have some external object inside my JavaScript file:
var data = [{id: 1, name:'Test1'}, {id:2, name: 'Test2'}];
which I pass to ReactDOM:
ReactDOM.render(<Test data={data}/>, document.getElementById('container'));
and as a property to the state object for Test component:
getInitialState: function () {
return {localState: data};
},
Somewhere along the chain, I use this:
handleClick: function () {
data[0].id=55;
this.setState({localState: data});
}
which causes re-render. Full code here: http://jsfiddle.net/44ff2j4b/
Is this a good idea? Basically, having external data which will be modified in place in the component and re-rendered appropriately. Are there some side effects of doing this? As far as I'm aware, it's not OK to modify state in the React component, but the "state" here does not belong to a component...it belongs to the domain logic.
In a React component, "state" is very much supposed to be modified, in-fact state of a component can only be modified within it. However, what you are doing here is seeding the state with a prop and then maintaining it internally. So long as you are not duplicating the prop every-time, merely seeding it, it is ok. Do read more about this here. You should however rename the prop from data to initialData to indicate that it will only be used for seeding the state.
Although it may work, it is not generally a good idea to do this.
React likes you to treat props as immutable. So if you pass data as a prop, you are not supposed to change the original data, like you do in:
data[0].id=55;
If you want to pass data to a react component, copy it as initial state in getInitialState(), and change only the copy inside the react component.
NB: for this, you will need to make a deep copy (so not only copy the array of objects, but also copy the objects themselves). ImmutableJS is a solution/ library often used in conjunction with react.
If you also want to change the original data, the proper way is to pass a function as a prop (in addition to the data prop) to the react component. From the react component, you can then call this function, which can then change the original data. The original data change or update should leave your data-copy inside react component intact.
The code (outside react) can then decide whether or not to re-render the entire react component with the new data (but it is standard practice to call ReactDOM.render() only once).