How to set props on dynamic created child - javascript

Within JSX syntax i can easily set a child prop to follow parent's state, like:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { attr1: 10 }
}
render() {
return (
<div>
<Child prop1={this.state.attr1} />
</div>
)
}
}
So whenever state.attr1 changes, Children will have it prop1 changed.
How can i get the same behavior with dynamic created childs:
class Parent extends React.Component {
constructor(props) {
super(props);
this.buildChildren = (items) => {
items.map(item =>
React.createElement(
childrenListWithClasses[item['childrenClass']],
{ key: item['id'], data: {} }
)
)
}
this.state = {
children: buildChildren(this.props.childrenList)
}
}
componentWillReceiveProps(nextProps) {
// maybe here i want to do something like
nextProps.data.forEach(item =>
ReactDOM.findDOMNode(item['id']).setProps({ data: item['data']}) )
}
render() {
return (
<div>
<div>{this.state.children}</div>
</div>
)
}
}
This piece of code does not work, also i've read that setProps is deprecated. Another approach would be create the child with props ~binded to this.state.data if data data is structured as obj:
React.createElement(
childrenListWithClasses[item['childrenClass']],
{ key: item['id'], data: this.state.data[item['id']] }
)
But seems like it tries to evaluate this.state.data[item['id']] at execution.
I've found a suggestion to use cloneWithProps, but this doesn't seem right... If JSX can change child's props why cant i do it with code? also it would not make if i want my child elements to smoothly transition when changing props, child's lifecycle would be ignored.

Related

Re-render child after parent state change with get request

So I'm a beginner with react and I was wondering how to re-render the child after setting the state in the parent (from the child). Here's a code sample. I have a function that calls a GET request using Axios and when I press the button in the child component ideally it will update the state in the parent and also re-render the child but it only does the former.
Parent:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
fetchData = () => {
axios
.get(url)
.then(res => this.setState({data: res.data}))
}
Render() {
return (<Child data={this.state.data} fetchData={this.fecthData}/>)
}
// ...
Child:
class Child extends Component {
// ...
render() {
const { data, fetchData } = this.props
// render data
return <button onClick={fetchData}>Change data then fetch</button>
}
}
Also, are you supposed to make a local state in the Child and set it as a copy of the Parent's state or just passing it down as a prop is okay?
Your parent component holds the data and the child uses it. It seems to me you're doing it the right way. Here is a fully working example:
Codesandbox
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
this.updateData = this.updateData.bind(this);
}
async fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
return response.json();
}
updateData() {
this.setState({ data: [] }) // Creates a flicker, just so you see it does refresh the child
this.fetchData().then((res) => this.setState({ data: res }));
}
render() {
return <Child data={this.state.data} onAction={this.updateData} />;
}
}
Note I renamed your child prop fetchData into onAction (I don't know what's the name of the action that triggers a refresh, could be onRefresh). It's always best to see components props with separation between data attributes and event attributes.
Even standard components have it this way: <input value={user.firstname} onChange={doSomething} />. So, better to prefix events by on, then the parent decides what to do with it. It's not the child's concern.
class Child extends Component {
render() {
const { data, onAction } = this.props;
return (
<>
<button onClick={onAction}>Change data then fetch</button>
{data.map((item) => (
<div key={item.id}>
{item.id} - {item.title}
</div>
))}
</>
);
}
}

Component has updated but no visual change

I'm pretty new to React and I'm pulling my hair out over this one:
HTML
<div id="root"></div>
JS
class Child extends React.Component {
constructor(props) {
super(props);
this.state = { title: props.title };
}
render() {
return ()<div>{this.state.title}</div>);
}
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = {
children: [
<Child title='a' />,
<Child title='b' />
]};
}
componentDidUpdate(prevProps, prevState) {
console.log('[componentDidUpdate]');
console.log('prevChildren: ' + prevState.children.map(c => c.props.title).join(', '));
console.log('children: ' + this.state.children.map(c => c.props.title).join(', '));
}
handleOnClick = () => {
console.log('[handleOnClick]');
this.setState((prevState) => {
return {
children: [
<Child title='c' />,
<Child title='d' />
]
};
});
};
render() {
console.log('[render]');
return (
<div>
<div>TEST</div>
{this.state.children}
<button onClick={this.handleOnClick}>CHANGE</button>
</div>
)
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
CodePen: https://codepen.io/robloche/pen/xmGMBy
What's happening in the console when I click the button is:
[handleOnClick]
[render]
[componentDidUpdate]
prevChildren: a, b
children: c, d
which looks pretty good to me but somehow, a and b are still displayed instead of c and d...
What did I miss?
Since you have an array of Child elements then React can't distinguish when they've actually been updated/replaced with new ones.
Add a unique key to each Child and it will work, e.g.
<Child key='a' title='a'/>,
<Child key='b' title='b'/>,
etc.
NB! When dealing with arrays of Components then the key property is mandatory and while it will help in this case as well, your current approach is not very optimal.
Instead of creating whole new Components on a state change you should instead store only their values (title in this case) and then have the render() method deal with it.
The following is pseudo-pseudo code as I only added the pertinent parts to show how it would work.
constructor() {
this.state = {
children: ['a', 'b']
}
}
onClick() {
this.setState({
children: ['c', 'd']
});
}
render() {
return (
<>
{this.state.children.map(title => <Child key={title} title={title}/>)}
</>
);
}
You should not use state in child component. you do not have any dependency of the child props, just use parent component's props and it should work fine.
Change:
return (<div>{this.state.title}</div>);
to
return (<div>{this.props.title}</div>);
The problem you are facing right now is that parent is changed to c,d and it has been passed as child too, but because react doesn't have any keys update or state updates it does not re-render the component. The best approach is to use the props passed from the parent and use them.
Demo

Passing child props to child component in Gatsby V2

Since upgrading to using Gatsby V2 I have been struggling to pass this.state to child components to be used as this.props.
For Example I have a Container that has data1 and data2 added to this.state as
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
data1: '',
data2: ''
};
}
componentDidMount() {
// Loading database
.then(doc =>
this.setState({
data1: doc.data().data1,
data2: doc.data().data2
})
);
}
render() {
const children = this.props;
const stateAsProps = React.Children.map(children, child =>
React.cloneElement(child, {
data1: this.state.data1,
data2: this.state.data2
})
);
return (
<div>{stateAsProps}</div>
);
}
}
and a child component as
class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<h1>{this.props.data1}</h1>
<p>{this.props.data2}</p>
);
}
}
and finally this is brought into the page by
const Page = () => (
<Parent authUserID="01234" campaignID="56789">
<Child />
</Parent>
);
In Gatsby V1 this was working but now with the migration I am receiving an error Uncaught Error: Objects are not valid as a React child (found: object with keys {authUserID, campaignID, children}). If you meant to render a collection of children, use an array instead.
Can anyone advise to why and how this issue can be rectified?
You are using the entire props object as children in your Parent component. Make sure you destructure out the children object from the props instead and it will work as expected.
const { children } = this.props;

Pass props to {children()} for use in component of child page

How can I pass props to a component of a child page?
The prop that I am trying to pass is onToggleBooking: PropTypes.func which is defined in my layout.js (root file) as
lass Template extends React.Component {
constructor(props) {
super(props)
this.state = {
isBookingVisible: false,
}
this.handleToggleBooking = this.handleToggleBooking.bind(this)
}
handleToggleBooking() {
this.setState({
isBookingVisible: !this.state.isBookingVisible
})
}
render() {
const { children } = this.props
return (
<main className={`${this.state.isBookingVisible ? 'is-booking-visible' : ''}`}>
{children()}
</main>
)
}
}
Template.propTypes = {
children: PropTypes.func
}
export default Template
I want to pass onToggleBooking={this.handleToggleBooking} prop to {children()} so I am able to pass and use in a component of one of the child pages.
To do this I tried
{
children.map(child => React.cloneElement(child, {
onToggleBooking
}))
}
But I receive an error of children.map is not defined.
First, it's ideal to render children via props like:
render() {
return <div>{ this.props.children }</div>
}
Rendering children as prop functions is doable but you hould take a look to ensure your class the children are extended from is configured correctly, and the resulting render template is valid:
class CoolClass extends Component {
render() {
return this.props.children()
}
}
And then the template you call when rendering should look like:
<CoolClass>
{() => <h1>Hello World!</h1>}
</CoolClass>
You are close with passing sown the toggle handler using onToggleBooking={this.handleToggleBooking}, but it needs to be provided as a prop itself on a component or child your passing down. You can either edit the constructor to include it with your props.children, but that may be a pain to debug correctly calling children() prop as a fucntion.

React - Check If Props Exist

I searched around the internet for an answer to this question, and I didn't find one. Therefore I am posting my question here.
I have a parent component (App) and a child component (Child).
The App component has a state with some data in it, like so:
class App extends Component {
constructor() {
super()
this.state = {
currentOrganization: {
name: 'name',
email: 'email'
}
}
render() {
return (
<Child data={this.state.currentOrganization} />
)
}
}
}
In my Child component, I have a form:
class Child extends Component {
constructor() {
super()
this.state = {
formData: {
name: '',
email: '',
}
}
}
render() {
return (
<Form ... />
)
}
}
According to the React docs, forms generally should have a state containing properties that correspond with each element of the form. The form that lies in my Child component must have the data of the currentOrganization (as seen in the App component) pre-populate into itself.
In order to accomplish this, I have to set the state of the Child to the props it receives from its parent.
What's the best way to check if my Child component received the props it needs in order to update its own state?
You can assign default props to component.
class Child extends Component {
constructor(props) {
super(props);
this.state = {
formData: {
name: props.name,
email: props.email,
}
}
}
render() {
return (
<Form ... />
)
}
}
Child.defaultProps = {
name: '',
email: '',
};
P.S.
props is JS object so You can check property like this
"prop_name" in this.props // true|false

Categories