Spread only relevant props to component - javascript

I have a simple component and I want it to have most (if not all) the default HTML element props, maybe extending React.HTMLAttributes<HTMLElement> and then spreading them in the component's attributes.
But the props' type also includes some non-default to be used elsewhere, let's say props.cardTitle. That prop isn't useful for the main container, but will also be included in the final HTML. I could manually exclude it when spreading the props, but there may be a lot of them.
So the question is, how can I exclude all non-default props when spreading them in the main container?
Here's an example to further illustrate this:
type Props = ParentProps & OtherProps;
type ParentProps = {
className: string,
// onClick, etc. maybe extend all html element props.
}
type OtherProps = {
cardTitle: string,
cardBody: string,
cardPrice: string,
}
export default function ProductCard(props: Props) {
return (
// Pass all props excluding "OtherProps" that have no use here
// and inside use other props normally
<div {...props}>
<p>{props.cardTitle}</p>
<p>{props.cardBody}</p>
<p>{props.cardPrice}</p>
</div>
);
}
I've been trying some unconventional ways to do it. Probably using an object inside the type with all "non-default" props, and then just excluding that one, would be my best bet. But this still has some extra syntax to call it from another component.
Since I'm both new to React and moving from JS to TS, figured it would be better to ask.

You can use object destructuring to strip them out.
Example
let {cardTitle, cardBody, cardPrice, ...remainingProps} = props;

Related

Set component prop to const array/object or re-create each render?

Curious in the performance or other benefits of using const variables where possible when setting a component's props verses setting them inline. Please include supporting ReactJS documentation if applicable.
Potential benefits of using const variables in my head:
Reduced overhead creating objects on every render.
Fewer cluttered code?
Fewer lifecycle/render loops? (This is my main concern.)
Example 1: Inline / Traditional
export const SomeContainer(...) => (
<Something validators={[required(), minLength(3), maxLength(10)]} />
);
Example 2: Via const Variables
export const SomeContainer(...) => (
<Something validators={Validators} />
);
const Validators = [required(), minLength(3), maxLength(10)];
In Example 1, the validators prop is a new array on each render while in Example 2, a constant value is passed to the validators prop.
Same question with:
inline vs. const objects ...
value={{a, b, c}}
value={ConstWithABC}
inline vs. const callbacks ...
onChange={value => setState({ x: value })}
onChange={handleChange} (handleChange is an instance function/const lambda)
React can track the state changes easily. So re-rendering the state owner component(let's call it XComponent) directly is the first point. Then, as you already know, a re-render flow will start upon all XComponent's children tree recursively.
While doing this, React will check prop changes to decide which children should re-render like:
if the prop is primitive, there will be a simple value comparison
if the prop is an array, function, or object(that's the place you were mentioning in your question), there will be a reference comparison directly. That's why there are two hooks for this purpose: useMemo and useCallback. These hooks help you to store the reference or value result from your in-component definitions/calculations. So, your second example most probably will not trigger a re-render because of the reference equality. And then React can stop trimming re-render tree after <Something /> and its possible children.
I experienced this while changing a prop array item in the past. I saw that React didn't even realize that I changed the prop array's length from 3 to 2 because of the same array reference. But I was seeing that there are still 3 items on the screen.
if the child is a result of some iteration or mapping like:
const Comp1 = (props) => {
return <div>
{props.data.map((item) => <Comp2 key={item.id} propX={item.x}/>)}
</div>;
};
then, there will be a key comparison for understanding which child should be added or removed from the render.
https://reactjs.org/docs/lists-and-keys.html#keys
And React docs suggest that you should use a primitive type for keys to make the comparison easier.
Overall, there is a doc for a better explanation https://reactjs.org/docs/reconciliation.html
basically your examples are all just values, I don't see much of a performance difference.
There will be a difference if you do the following, however.
<Something value={[1000 numbers array].reduce((a,b) => a+b))} />
vs
const someNumber = useMemo(() => [1000 numbers array].reduce((a,b) => a+b)), [1000 numbers array])
return <Something value={someNumber} />
In above example it's calculation x 1000, vs calculation x 1, if you were to render the page 1000 times.
and if you don't want const Validators = [...] to be re-rendered every time, you can basically put it outside the component as global const variable and it gets rendered only once.
E.g. if you want to declare a styled component, it's always advised to declare the styled component OUTSIDE the component, so that you only render it once.
And with regards to your following example.
inline vs. const callbacks ...
onChange={value => setState({ x: value })}
onChange={handleChange} (handleChange is an instance function/const lambda)
You are just referring to a single setState. What if it's function with 100lines of codes? You gonna throw that in the return clause?
Many times, other than performance, we also consider readability. Even if writing the function within the return clause provides better performance, I will never do that.

Can I use a template literal inside a JSX tag?

I basically want to have variable components rendered inside a component I'm making which will vary depending on input provided by a user in the prop, but I want to avoid unnecessary if statements and so on.
Would I be able to do that? For example, if the weather prop is defined by the user as Sun, then <{weather}/> would be the same as <Sun />.
In React, names starting with a capital letter will compile to the createComponent method. So with that in mind, the correct solution is to assign the name value to a capitalized variable (see below).
const WeatherComponent = props => {
const ComponentTagName = props.weather;
return <ComponentTagName />
}
Another technique I like to use is mapping child components in an object. This makes the component easier to configure (and reuse) as only an abbreviated key is required instead of the full component name.
import React from 'react';
import SunComponent from './sun-component';
import RainComponent from './rain-component';
const WeatherComponents = {
Sun: SunComponent,
Rain: RainComponent
};
const WeatherComponent = (props) => {
const TagName = WeatherComponents[props.weather || 'Sun'];
return <TagName />
}
export default MyComponent;
You can't really mix JSX syntax with template literals (although anywhere in JSX you could put {a js expession} you could use a template literal, you can't put an expression where a component name is expected).
But since Sun is just a variable with a value that is a component, if you were to call your prop Weather instead of weather you could: <Weather />
If weather contains a reference to the Sun function, you can use weather directly (not via a template literal), but in lower case JSX will assume it's an HTML tag, not a component, and put it in quotes in the resulting React.creatElement call; so you have to use an initially-capped variable (Weather instead of weather):
<Weather />
If weather contains the string "Sun", you can't directly use it to create a Sun element; you need a reference to the Sun function, not a string. You need a way to get from the string to the function (an object to look it up on, a Map, etc.).
You can use template literals elsewhere in JSX, within {}, since those contain JavaScript expressions. But not to define the component for a tag.

React component not rerendering when props changes

Here are my 2 React components - Parent and Child
class ParentComponent extends Component{
render(){
/* consultations comes from redux store
and looks like { consultation_id:123, date:10/12/2013 } */
return(
_.map(this.props.consultations,(consultation)=>{
return{
<ChildComponent consultation={consultation} />
}
})
);
}
}
class ChildComponent extends Component{
render(){
return(
<div>this.props.consultation.date</div>
);
}
}
My problem is that I have certain actions that modify the consultations object. For eg: the props.consultation.date changes in the parent component, but the child component does not re render to show the latest consultation date.
However, I noticed that if I send each item in the consultations object to the child component like <ChildComponent date={this.props.consultation.date} /> it rerenders when the date changes!
Any idea why React does not re-render components when props sent as an object change?
I could always do with the work around but wondering if this is a bug or am I missing something?
The solution is to change consultation={consultation} to consultation={...consultation}. I am still unsure why, but it works!
You should definetly add the key prop with consultation_id to the child element. React can have problems rerendering elements without a key prop!
When you map over an array of items, you need to pass a unique key prop to each item. This signals to react which element is which.
...
_.map(this.props.consultations, consultation => {
return (
<ChildComponent
key={consultation.id}
consultation={consultation}
/>
)
})
The details you specified says that you change the date of same object~consultation in parent component and you parent component is getting the data as props.
Now if you mutating the same consultation object it won't make component re-render.
consultation.date = /* some other date */;
it'll not re-render the component.
But if you change the reference of the object like:
newConsulationData = { ...consultation }
newConsultationData.date = /* some other date */;
It'll work fine.
In your scenario you might have trouble as you directly mutating the props array object, and passing the same array so I suggest you change the reference of consultations array:
You'll need to
newConsulations = [ ...consultations ];
newConsultations[index of consultation].date = /* some other date */;
This should solve your problem.
When working with react try not to mutate the objects if you want to re-render the component on the changes .
I think it may be related to data type.
The first approach is passing an Object to child component,
<ChildComponent consultation={consultation} />
the second approach is passing a String to child component.
<ChildComponent date={this.props.consultation.date} />
When the property date of the object consultation is changed, the object's index is not changed. I think react is referencing an object by its index. Object returns an index not the exact value. The index is the specific address where object is stored in memory. But a string or boolean or number returns the value directly.
I think react is comparing object by index, and comparing string by the value. In the first approach, index does not update, so re-rendering is not happening.
The third approach extracts the object properties with spread symbol.
<ChildComponent consultation={...consultation} />
I think react is referencing the property itself in this approach, as each property is extracted. It's no longer referencing the object, so re-render works.
(I used a few I think statement here, as it's just my guess. Still need official documents to support.)

React.cloneElement: pass new children or copy props.children?

I'm confused by the third "children" parameter of React.cloneElement and it's relation to this.props.children.
I followed this guide on higher order components and have the following code:
render() {
const elementsTree = super.render()
let myPropChange = {}
/* work on newProps... */
myPropChange.something = "nice!".
const newProps = Object.assign({}, elementsTree.props, myPropChange)
/* map children */
const newChildren = React.Children.map(elementsTree.props.children, c => something(c))
return React.cloneElement(elementsTree, newProps, newChildren)
}
Should I put the mapped children into my newProps.children or should I pass them as the third parameter to cloneElement?
Object.assign copied the children from props to newProps anyway, should I skip them?
In the guide it says
Components don’t have a guaranty of having the full children tree resolved.
What does that mean in my situation? That this.props.children is not there?
Added 4th question: Why should I clone the props at all and not just directly edit them?
Should I put the mapped children into my newProps.children or should I pass them as the third parameter to cloneElement?
Either should be fine.
Object.assign copied the children from props to newProps anyway, should I skip them?
When using cloneElement, you don't need to copy the props yourself. You can just do React.cloneElement(elementsTree, {something: 'nice!'}).
In the guide it says "Components don’t have a guaranty of having the full children tree resolved." What does that mean in my situation? That this.props.children is not there?
I can't be sure what that article meant, but I believe the author means that your component can choose not to use this.props.children. For example, if you render <Foo><Bar /></Foo>, then Foo will receive <Bar /> in this.props.children, but if Foo doesn't use this.props.children in its render method, then Bar will never be rendered.
Why should I clone the props at all and not just directly edit them?
React elements are immutable and there's no way you can change the props after an element is created. This allows some performance optimizations in React which wouldn't be possible otherwise.

React: how to compare current props.children with new one

Hi,
i am building component which acts only as wrapper for some other generated content and uses third party library. This library works with props.children of component. So far so good, but this thrird party library is little laggy when applied, or refreshed on element. And because only reason to refresh this library is when props.children changed I am trying to figure how to compare this.props.children and nextProps.children in shouldComponentUpdate. I was thinking that PureRenderMixin should do the work, but for me it does not works. Component is rerendered even if I change only state.listName as it is in example below.
<div>
List name '{this.state.listName}'
<br />
<MyComponent>
<ul>
{listOfLi}
</ul>
</MyComponent>
</div>
Is there any way, how to manage comparing of props.children or any other option how to do something like that?
Thanks for any help!
As Matt S pointed out, the accepted answer is kind of a fragile workaround and would depend on a non-standard use of key. Aside from the list examples he listed, even using something like key={id} would fall down if your ids remained the same but certain fields were modified in the resources they represent.
This issue contains a good discussion on the topic and ends with a more stable workaround. Essentially, you can simplify the children prop in a way that allows you to run a deep comparison. You can use the React.Children utilities to write the simplification methods:
// Flattens all child elements into a single list
const flatten = (children, flat = []) => {
flat = [ ...flat, ...React.Children.toArray(children) ]
if (children.props && children.props.children) {
return flatten(children.props.children, flat)
}
return flat
}
// Strips all circular references and internal fields
const simplify = children => {
const flat = flatten(children)
return flat.map(
({
key,
ref,
type,
props: {
children,
...props
}
}) => ({
key, ref, type, props
})
)
}
Then you can use shouldComponentUpdate or React.memo to prevent re-renders:
const MyComponent = ({ children }) => (
<div>{ children }</div>
)
export default React.memo(MyComponent, (prev, next) => (
JSON.stringify(simplify(prev.children)) ===
JSON.stringify(simplify(next.children))
))
These utilities + JSON.stringify are just one approach, the one mentioned in the comment is similar and you could also leverage utilities like lodash.isequal for the deep comparison. Unfortunately I don't know of any one or two liners for this comparison but please comment if you know a simpler stable way to do this!
You can make use of child key prop that React suggests that arrays of children should be given to uniquely identify them. Because each child has a key, you can reliably tell whether children has changed across prop changes (this is the entire point of keys!). If the keys don't match between new and old then they have changed.
React.render(<App><Child key='1'/><Child key='2'/></App>, document.body)
and do the check in the App component you want to check before each update if the children changed
shouldComponentUpdate(nextProps){
var oldKeys = this.props.children.map( child => child.key);
var newKeys = nextProps.children.map( child => child.key);
//compare new and old keys to make sure they are the same
}
Note that this doesn't tell you if the content of each child has changed, you have to compare each by some criteria (such as deeply comparing props) if you want to know if nothing in the whole tree below this point has changed
as an even further optimization we know that children will never change as result of a state change so we can actually do our comparing in componentWillReceiveProps() and just set some state property like childrenHaveChanged
Something about the design and behavior you describe is off. You should rarely, if ever, have to concern yourself with performing manual diffs of children. That should be left to React. If the library you are using is choking every time the component updates, regardless of whether it's because of children or some other state/prop change, it is not written reactively and you should look for a different one that is written reactively. Or you could contribute to fixing that behavior in the open source project, either by opening an issue or submitting a pull request.
It's not easy to do what you're trying to do because it shouldn't be necessary. React is very, very good at handling reconciliation and will only render when something has actually changed that will change the state of the DOM relevant to it.

Categories

Resources