React - transfer props to variable that's already a React element - reactjs

Assume you have a code:
const method = Component => {
const someProps = { foo: 'bar' };
// Add those props to the Component and render it
};
And you use it like this:
method(<MyComponent />)
What should I put in method so I can pass the someProps further?

Class-based syntax
render() {
return <Component {...this.props}>;
}
Functional based syntax
const method = Component => {
const someProps = { foo: 'bar' };
return <Component {...someProps} />;
};
If you wish to find out more on this topic, it's aka HOC, higher order component

Unfortunately, this piece code will not return a component for rendering:
const method = Component => {
const someProps = { foo: 'bar' };
// Add those props to the Component and render it
};
You want your HOC method to be like this:
const method = Component => props => {
const someProps = { foo: 'bar' }
return <Component {...someProps} {...props} />
}
...someProps is the extra props that is 'injected' into Component through HOC. Usually this comes from some API calls inside HOC method.
While ...props is the 'normal' props that is passed down into Component when calling it.
To illustrate what I meant:
import FooComponent from './FooComponent'
// Using the HOC:
const FooComponentWithMethod = method(FooComponent)
// ...rest of code
render() {
return <FooComponent hello={'world'} />
}
// ...rest of code
When you console.log(this.props in FooComponent, you will see both
{
foo: 'bar', // injected by 'method' HOC
hello: 'world' // passed down from parent
}

Seems like the morning brought the answer:
To get this Component with new props, and to keep the old one as well:
const method = (Component) => {
const customProps = { foo: 'bar' };
const elemProps = Component.props;
const mergedProps = { ...customProps, ...elemProps };
const cloned = React.cloneElement(Component, mergedProps);
};

Related

Passing object to child component loses its properties and methods

I have App.js that has a component in it where I pass down props:
let chess = new Chess();
// lots of code...
console.log("chess in App.js", chess);
return <ChildComponent chess={chess} />;
The ChildComponent.js receives this prop as follows:
const ChildComponent = (chess) => {
console.log("chess", chess);
};
When I inspect that in Chrome, I get this:
So, I am somehow losing the object detail when I pass it down as props. Is there something obvious that I am doing wrong?
It needs to be in a {} because it's inside a props object
const ChildComponent = ({ chess }) => {
console.log("chess", chess);
};
OR
const ChildComponent = (props) => {
console.log("chess", props.chess);
};
Your code, log the props object,
try this
const ChildComponent = (props) => {
console.log('chess', props.chess)
}
or
const ChildComponent = ({chess}) => {
console.log('chess', chess)
}

Trigger Child component method when prop changes

I'm trying to make a child functional component to update when a Parent component changes a value in its state that I'm passing to a this child component as a prop.
The child component "receives" the value correctly and displays the prop value, but the method does not run again.
Child component
import React from 'react'
const MyCustomTable = props => {
const {
data = [],
} = props
const finalData = getSalesData() //This is the method i want to run when the selectedMonth prop updates
const getSalesData = () => {
//It does some calculations with the prop called data
}
return (
<Box>
{JSON.stringify(props.selectedMonth.value)}
<Table
data={finalData}
/>
</Box>
)
}
SalesByFamilyBU.propTypes = {}
export default MyCustomTable
The JSON.stringify line displays the changes correctly but I guess the getSalesData() is not automatically executed.
While you could use some lifecycle method or the useEffect hook to achieve what you want to do, I would rather use a functional approach.
In your example, finalData is a derived value of props.data and props.selectedMonth. You could then compute finalData directly from these props:
const MyCustomTable = props => {
const {
data = [],
} = props;
const filterData = (data, selectedMonth) => data.map(dataPoint => ({
...dataPoint,
selected: dataPoint.month === selectedMonth,
}); // or whatever, use your function logic here
const finalData = filterData(data, props.selectedMonth.value);
return (...);
};
If you really needed to call a function each time data is changing (ex. to fetch data elsewhere) you could use something like that:
const MyComponent = ({ data }) => {
const [finalData, setFinalData] = useState([]);
const myFunction = () => {
const newData = ... // whatever you need to do
setFinalData(newData);
};
useEffect(myFunction, [data]);
return ...;
};

How to type a component that is a child of a parent component that had its props dynamically added to?

I have two components -- one being a child and the other a parent. The parent component is designed to be a HOC that makes an ajax call for data, and then it passes that data down to its child. The props that it passes down to the children is dynamically added through React.CloneElement with the this.props.children.
With regards to flow, it isn't able to interpret/see the additional props I've added when re-cloning.
How do I get past this?
How both components relate.
<ParentComponent>
</ChildComponent />
</ParentComponent>
const ParentComponent = (props) => {
// code
return (
<>
{React.cloneElement(this.props.children, { extraProps }}
<>
);
}
type PropTypes = {
extraProps: string, // flow complains, and I don't know how to get it to recognize that the extraProps is added dynamically.
};
const ChildComponent = (props) => {
// code that uses the additional `extraProps`
}
So far I haven't found a good way to get it to work completely. Here's some code that will set it in the right direction though.
import * as React from 'react'
const ParentComponent = (props: { children: React.Element<typeof ChildComponent> }) => {
return (React.cloneElement(props.children, { extraProps: 'hello' }));
// return (React.cloneElement(props.children, { extraProps: 1 })); // <-- this will error
}
type PropTypes = {
extraProps?: string, // not great because you'll still have to check presence, but at least it provides
// type checking on extraProps in the ParentComponent
};
const ChildComponent = (props: PropTypes) => {
return <p>{props.extraProps}</p>
}
<ParentComponent><ChildComponent /></ParentComponent>
Flow Try
Flow has some great documentation on typing higher-order components. It changes your implementation slightly, but I think it should achieve the same effect as your code. Using the documentation's example for injecting props, you could write,
import * as React from 'react';
type Props = {
x: string,
y: string,
}
function injectY<Config: {}>(
Component: React.AbstractComponent<Config>
): React.AbstractComponent<$Diff<Config, { y: string | void }>> {
return function WrapperComponent(
props: $Diff<Config, { y: string | void }>
) {
return <Component {...props} y="injected" />;
}
}
function Child({ x, y }: Props) {
return x + y;
}
const EnhancedChild = injectY(Child);
function App() {
return (
<div>
<Child x="explicit" y="explicit" />
<EnhancedChild x="explicit" />
</div>
);
}
Try Flow
The injectY prop is generic right now so you can reuse that pattern for other places where you might be using React.cloneElement. I'm not familiar with React.cloneElement so I can't say much about the differences between the cloning and HOC approaches other than you'll probably get better type checking with injectY.

flow complaining about defaultProps on connected component

I have a connected component like this:
const ConnectedComponent = connect((state, props) => {
return {
//fields
};
}, mapDispatchToProps)(Component);
ConnectedComponent.defaultProps = {
// fields
};
But flow says:
ConnectedComponent.defaultProps = {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment of property `defaultProps`
ConnectedComponent.defaultProps = {
142: };
^ object literal. This type is incompatible with
139: ConnectedComponent.defaultProps = {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ undefined
You cannot apply defaultProps to a component which doesn't extend React.Component.
Also, in your case your ConnectedComponent is directly connected to store, so there are no props whereas the component Component will receive props from ConnectedComponent, so you should add default props over Component to set default values.
const ConnectedComponent = connect((state, props) => {
return {
//fields
};
}, mapDispatchToProps)(Component);
//This should be added to the Component wherever it is declared
Component.defaultProps = {
// fields
};
Let me know if it helps.

React assign key to already rendered component

Is it possible?
I have a component where children are rendered by an arbitrary mapping function coming in as props. A simplified example:
class SomeComponent extends Component {
render() {
const { renderChild, businessObjects } = this.props
return <div>
{businessObjects.map(renderChild)}
</div>
}
}
I obviously get a warning saying children are rendered without the key attribute.
I tried assigning the key after the vdom element is rendered:
...
{
businessObjects.map(e => {
const vdom = renderChild(e)
vdom.key = e.id
return vdom
})
}
...
But the object returned from the JSX transform is frozen, so I can't do this. Also there is no API to temporarily unfreeze then re-freeze objects in js. Cloning is out of question for performance reasons (thousands of components are rendered like this)
What can I do?
Again, for performance reason I can't wrap the rendered children into another component, so a solution like this wouldn't work:
const Child = ({renderChild, bo}) => (<div>{renderChild(bo)}</div>)
// in SomeComponent
...
{
businessObjects.map(e => (<Child
key={e.id}
bo={e}
renderChild={renderChild}
/>)
)
}
...
Update
The reason for this structure is that SomeComponent is a dumb component, and has no access to application state (redux). But the rendered children do need to have access to dispatch (I do it in a form of connected action creators).
So you can imagine the whole thing like this:
const createChildRenderer = ({actionFoo, actionBar}) => (obj) => {
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in a connected component
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const renderChild = createChildRenderer({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
renderChild={renderChild}
businessObjects={this.props.businessObjects}>
}
}
The way I ended up solving this by taking an actual react component as an argument:
So that in the dumb component that previously took a renderer function, now I take a component:
class SomeComponent extends Component {
render() {
const { ChildComponent, businessObjects } = this.props
return <div>
{businessObjects.map((o) => (<ChildComponent
businessObject={o}
key={o.id}
/>)}
</div>
}
}
And where I previously created the renderer function, now I create the component:
const createChildComponent = ({actionFoo, actionBar}) =>
({ businessObject: obj }) => { // this is now a component created dynamically
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in the connected component:
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const ChildComponent = createChildComponent({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
ChildComponent={ChildComponent}
businessObjects={this.props.businessObjects}>
}
}
You can use cloneElement on the child received from renderChild:
React.cloneElement(
child,
{...child.props, key: yourKeyValue}
)

Resources