difference between using children prop directly and using React.Children.toArray method - javascript

I was playing with React.Children and children property and i found that there is no difference between using children.filter() and using React.Children.toArray(children).filter i was also able to render a specific child from the children props by using it's index directly and treat it like a normal array
so i wonder is there any difference between the two approaches? and if i can use the props directly with the arrays method why the react team implemented the toString() method in the first place
i tried console.log() the two of them and i see no defference between the two approaches
const ChildrenContainer = (props) => {
const { children, howMany } = props;
console.log(children, '\n', Children.toArray(children));
return (
<div className="card-columns">
{Children.map(children, (thisArg) => thisArg)}
</div>
);
}

If you look at the docs here: https://reactjs.org/docs/react-api.html#reactchildren you will notice some things.
React.Children deals with null and undefined. Calling children.filter() would error should children be null or undefined.
It deals with <Fragment> children
Includes helper functions such as .only() that provide more react specific capabilities than a regular array.
toArray() alters keys to preserve semantics of nested arrays.
So in your specific case, you might not notice any difference. But even if you are certain that children will always be a usable array, it is still good practice to use React.children. This is because it abstracts away the children concept so that your code is more future proofed (for changes to your code, and potential changes to react itself).

Related

Why does mapping a React hook work, but a lambda violates rule of hooks?

If I have a hook, e.g:
const useGetSet = (label: string) => {
const [get, set] = useState(label);
return { get, set };
};
I can map it over an array, e.g:
const labels = ['one', 'two'].map(useGetSet);
But if I expand this to a lambda, e.g:
const labels = ['one', 'two'].map((l) => useGetSet(l))
Then it causes:
React Hook "useGetSet" cannot be called inside a callback.
React Hooks must be called in a React function component or a custom React Hook
function.
(react-hooks/rules-of-hooks) eslint
Why this difference, shouldn't they be equivalent?
Additionally, if this is a violation of the rule of hooks, how should this be done?
Full working example here.
It's bad in both cases, but ESLint isn't picking up the first case.
You can see this by changing the labels array at run-time. If you increase or decrease the length it blows up (because an unexpected number of hooks was called, as per the exception), if you don't it doesn't re-render as expected. See here for an updated version of your demo.
(The reason that the rule exists is because under the hood, hooks need to be called in the same order every time. Obviously, if the number of hooks varies between renders then the order changes.)
In your specific case, because your array is fixed length and not changing then your code will work fine (no matter what the linter says). However, it's still a dangerous pattern and best avoided.
In terms of how to handle it better, if you need more than basic state management something like useReducer would work. You can have as may labels as you like and define setter/getter actions that take the label as an argument.
Let analyze the two scenarios:
const labels = ['one', 'two'].map(useGetSet);
In this case the useGetSet is called inside the hook is called "inside custom React Hook function".
const labels = ['one', 'two'].map((l) => useGetSet(l))
In this case the useGetSet is called inside an anonymous function, so the "rule of hooks" is broken.
So basically:
In the first scenario: Hook > call of the hook
In the second scenario: Hook > anonymous function > call of the hook
Related to the last question:
Why this difference, shouldn't they be equivalent?
No, they are not equivalent.
In the first case the callback function reference is the function named, by you, useGetSet.
In the second case the callback function reference is a new function, defined anonymously.
An interesting explanation about why this rule is so important can be found in the documentation, in particular in this section:
https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
But also consider that there are cases when it is safe to disable the linter rule, as explained in depth here:
Why can't React Hooks be called inside loops or nested function?
In this example useGetSet is the callback.
const labels = ['one', 'two'].map(useGetSet);
In this example your calling the hook inside the callback so the error makes sense. This answer covers pretty nicely why the rule exists: Why can't React Hooks be called inside loops or nested function?
const labels = ['one', 'two'].map((l) => useGetSet(l));
You have 2 options here:
If you need to call the hook inside of a callback but the hook is not rendering anything then you need to create a new hook e.g https://codesandbox.io/s/sad-cache-zti5f?file=/src/TestComponent.jsx .
If the lambda is rendering smth at the end then you can create a new component
e.g https://codesandbox.io/s/quizzical-currying-pe15r?file=/src/TestComponent.js.

Does object creation in the render method break 'React.PureComponent' and render checking in 'shouldComponentUpdate'?

tl;dr: when you create objects / components in the render method to be passed as props / children to the rendered components, does it fail the checking of React.PureComponent and prop checks in shouldComponentUpdate?
I'm seeing patterns similar to this a lot:
render() {
const commentTrigger = <Button>Comment</Button>;
const deleteTrigger = <Button>Delete Thread</Button>;
const someComponentProps = {
prop1: this.getProp1()
prop2: this.props.prop2
}
return (
<div>
<SomeModal trigger={commentTrigger} />
<SomeOtherModal trigger={deleteTrigger} />
<SomeComponent {...someComponentProps}
</div>
)
}
would this fail the .PureComponent check or shouldComponentUpdate check in SomeComponent, since someComponentProps is a different object every time, and in particular (assuming this.getProp1() is a newly created object) would prop1 alone cause these tests to fail and end up re-rendering the component? How about if this.getProp1() is just a number?
Otherwise, is there any good reason to not write React components in this way?
Thanks for your help in advance. Let me know if I should rephrase the question or clarify anything.
PureComponent will do a shallow comparison of its props, basically using the Object.is() comparison. In the case of objects, that means it will only pass if it's the same object, not if it's a different object with all the same properties.
So your first two examples (commentTrigger and deleteTrigger) are going to run into this. They're brand new objects each time you render, unrelated to the previous ones, and thus will not pass a triple equals.
In your third example (spreading someComponentProps), it makes no difference whether someComponentProps is a new object or not. By spreading it, you are passing in a sequence of individual props, starting with prop1={someComponentProps.prop1} and running through the rest of the properties on the object. It's a convenient syntax when you don't know how many props there are. If SomeComponent is a pure component, it will check whether prop1 changed and whether prop2 changed, with no idea that someComponentProps even existed.
PureComponent does shallow equality check of props object. In case next prop1 value (created with this.getProp1()) is === equal to previous value, so is prop2, SomeComponent won't be updated.
Even if props aren't === equal, custom checks can be implemented in shouldComponentUpdate, e.g. deep equality check.

How does React know if a function component is a React component?

React supports both class and function components. All React files have an import React from ‘react’ statement at the top. When creating a class component (like so: class MyClassComponent extends React.Component ...), there is an explicit mention of which class this class instance extends from. However, when declaring a function component (const MyFunctionComponent = () => ()), it isn’t being explicitly said “hey this is a React component by the way.”
For all we know, this could be a function in plain JavaScript. How then, does React recognize functions as function components?
Here is the code for the isValidElement() function. In a nutshell, they check if it is an object (functions are objects) with a $$typeof of the REACT_ELEMENT_TYPE Symbol.
Beyond that though, it doesn't really matter if it is a normal function or not. For the most part, even if I make some random function, as long as it has a render() function, it'd be usable as a React element. I'm not sure if they have checks for missing lifecycle functions or not, but if they do, then it would work (if they don't, it'd throw errors).
When it comes to JavaScript "inheritence", it always just boils down to if the shape of the object matches the expected shape.
It doesn't do that, every function is valid, if it returns a valid type. However, React doesn't check statically if a function will return something valid, so it just runs it, if it's a function. In newer React versions the return value can be nearly everything. Arrays work now, strings also. Currently React does support the following types as return values:
React elements. Typically created via JSX. An element can either be a representation of a native DOM component (), or a user-defined composite component ().
String and numbers. These are rendered as text nodes in the DOM.
Portals. Created with ReactDOM.createPortal.
null. Renders nothing.
Booleans. Render nothing. (Mostly exists to support return test && pattern, where test is boolean.)
Fragment (array of valid elements)
So, unless you don't return undefined, it should work, but as said, it will execute it always if it is a function.
But it IS a plain javascript function. It simply return a value which is then interpreted by React.
Edit: To help you understand, you need to keep in mind that in JavaScript EVERYTHING (except of course primitive types) are objects (Classes, Functions, Arrays, etc) are at the end all the same.

Does it make a difference if you don't use a prop in your PureComponent render method?

Say I have a PureComponent with a bunch of props. If I don't use every single prop in my render method, will it affect anything at all?
For instance, if I depend on componentWillReceiveProps being called for every single prop, even the ones that are not rendered...it will still be called, correct?
I can't imagine the React authors doing anything else... I can probably hunt down the code and take a peek myself and I will if I can't get a definitive answer here. Thanks.
NOTE: The reason I'm asking is because I'm wondering I see some code that is pulling unused variables from props here.
PureComponent is just like a regular component, only it implements the lifecycle method shouldComponentUpdate(). This function will ultimately determine if render() will run, so it cannot possibly rely on render() itself.
For instance, if I depend on componentWillReceiveProps being called for every single prop, even the ones that are not rendered...it will still be called, correct?
Yes, that is correct.
The code you referenced contains a common pattern, where props should be passed down, but not as a whole. The omitted props are commonly known as ownProps, which indicates that they belong solely to the component itself.
If we look at the code:
const {
element,
onChange: _onChange,
value: _value,
minLength: _minLength,
//...
...props
} = this.props;
In this case, value, minLength and debounceTimeout are only assigned to variables so they will be excluded from the props variable, which is used afterwards.
return React.createElement(element, {
...props,
//...
}
For instance, if I depend on componentWillReceiveProps being called for every single prop, even the ones that are not rendered...it will still be called, correct?
Yes componentWillReceiveProps will still be called regardless of what effect a particular prop may or may not have on rendering.
even the ones that are not rendered
Note many props are not rendered themselves, but they may have some effect on rendering (or they may not). React would not know if a particular prop affected rendering at least until the new virtual DOM was constructed.

Destructuring ImmutableJS Map for React Component Props

We have a project using React + Redux + ImmutableJS. One of our engineers recently added a helper method to support destructuring an ImmutableJS Map when passing it to a component as props:
export function toObjectShallow(mapping: Map) {
const result = {};
mapping.map((value, key) => {
result[key] = value;
});
return result;
}
Thus, we can still do the following and avoid being verbose with repeated calls to Map.get:
<XyzComponent {...toObjectShallow(this.props.xyz)}/>
Yes, that's essentially making two shallow copies (our method + destructuring) of the original object. That should be of minimal expense. I'm wondering though, since I don't see this kind of recommendation really anywhere else in the React/Redux/Immutable communities, is there something else I'm missing that would make this unideal?
The passed properties are still the original, immutable props. It's just the containing object is mutated which doesn't matter, because it's not getting passed to the component anyways. So, what gives? This seems like such a simple solution while avoiding toJS(). Why isn't it really mentioned anywhere?
I followed the advice in the redux docs to use a HOC for all connected components to allow me to interact with plain javascript objects outside of redux. So my selectors and reducers still use ImmutableJS objects, but the rest of the code uses plain javascript objects:
https://redux.js.org/docs/recipes/UsingImmutableJS.html#use-a-higher-order-component-to-convert-your-smart-components-immutablejs-props-to-your-dumb-components-javascript-props
edit- not sure if this is the toJS you are mentioning above, I had assumed you meant ImmutableJS.toJS.
as far as preferences, using an HOC you only need to do it once per component, as opposed to each time you use a component in your method.
I think the "correct" answer I was looking for was to continue using Immutable JS's toObject() which does a shallow conversion of an Immutable Map object to a regular object, thus allowing us to continue using syntactic sugar while keeping the properties immutable and not having to use our own shallow copy.

Categories

Resources