Passing object to child component loses its properties and methods - reactjs

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)
}

Related

Attach a property to a functional component wrapped with React.forwardRef

I have a component wrapped with React.forwardRef and it so happens that it is a compound component as well.
I'm not sure how do I maintain
Form.Item = FormItem;
while the Form component function being wrapped with forwardRef.
const Form = React.forwardRef((props, ref) => { return <form></form>});
Typescript gives me an error (rigthly) saying
Property 'Item' does not exist on type 'ForwardRefExoticComponent>'.ts(2339)
Just to make it more clear of the solution, as it's on an external site (Credits to original author here, and thank you #Antoan Elenkov)
export interface Props = {
...yourPropsHere;
};
export interface CompoundedComponent extends React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLInputElement>> {
yourStaticFunctionOrSomethingLikeThat: () => void;
}
const Component = React.forwardRef<HTMLInputElement, Props>((props, ref) => (
<input ref={ref} {...props} />
)) as CompoundedComponent;
Component.yourStaticFunctionOrSomethingLikeThat = () => {};
This solution from the same GH thread seems nicer to me:
const Form = React.forwardRef(function Form(...) {
...
})
const FormItem = React.forwardRef(...)
const FormNamespace = Object.assign(Form, {Item: FormItem})
export {FormNamespace as Form}

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 ...;
};

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

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);
};

Render body from function react

I have the following React class
class Cards extends React.Component {
//state
//axios-> set new state
render(){
const{t} = this.props;
return(
// something
<RenderCards t={t}/>
)};
}
And my RenderCards:
let RenderCards = (props) => {
const {t} = props;
// do something with t
}
The thing is that I also want to pass the state with the props but the following approach is not working :
<RenderCards t={t} st={st}/> //st is this.state
let RenderCars = (props,st) =>{
const {st} = st; // -->is undefined!
}
How should I properly pass the state? Thank you!
All of the things that you pass as JSX attributes will go into the props argument of your stateless component; it doesn't matter where they came from originally. So the signature of RenderCards will always be props => JSX.Element:
let RenderCards = (props) => {
const { t, st } = props;
// do something with t and st
};
Personally I would remove the definition of RenderCards outside of the parent component as well, to avoid unnecessarily redefining it every time the parent is rendered.
Here <RenderCards t={t} st={st}/> the state is passed as a prop. It won't be available as second function parameter (which is now deprecated context):
let RenderCards = (props, st) =>{...}
It is:
let RenderCards = (props) =>{
const {st} = props;
...
}

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