How do I change state on a component two levels up? - reactjs

I'm learning React and I've started extracting components. I understand how to bind an event like onClick on a child component, but what about a grandchild?
Example:
I have a List component. It has ListRow children. Within each ListRow child, I have a button component for deleting that particular row from the parent (List). My thoughts are that thedeleteRowclick handler would be on theListcomponent so that I could then set the state. However, I can't seem to find a way to call the grandparent's (List`) eventHandler.
<List /> // has event handler as well as state for the list items
<ListRow />
<DeleteButton /> //when clicking this i want to delete parent <ListRow />
Am I just supposed to pass the onclick down the chain?

When creating components you have to decide whether or not a component is a functional component or a component that needs to manage state. Here is an example where you have a "Grandparent" that passes down functionality to it's child and the child to it's child. If a component does not need to manage state you make it a "functional component" like the "Parent" and "Child" examples below:
class GrandParent extends Component {
handleState = (obj) => {
this.setState(obj);
}
render() {
return (
<Parent handleState={this.handleState} />
);
}
}
function Parent(props) {
render() {
return (
<Child handleState={props.handleState} />
);
}
}
function Child(props) {
render() {
return (
...
);
}
}
You want to pass it down along and wherever you need to call the function you can use it as props.handleState() from whatever component that you send it to.

You could try something this:
// grandparent function that goes into parent
heirloom()
{
console.log("grandparent says hi");
//something happens
}
// everything else (put into all subsequent children)
heirloom()
{
this.props.heirloom();
}
<List heirloom="this.heirloom">
<ListRow heirloom="this.heirloom" />
<DeleteButton onClick="this.heirloom"/>
My syntax may be off and this may or may not work, I haven't had the chance to play around with React for a while. If it does, great! If it doesn't, let's just hope someone with a better answer comes along ^^

Related

How to pass props from functional component to class component

I'm quite new to functional components and I've been on this for hours.
I simply want to pass a prop from a parent functional component to a class child component so that I will use the updated prop (which will be a boolean) to change the display of an element. That's all.
In this scenario, I want to switch the sidenav from opened to closed based on the prop.
Here is the parent component (functional):
let opened = false;
function openSidenav(){
opened = !opened;
}
const [sidebarOpened, setOpened ] = useState(opened);
return (
<Sidebar sidebarOpened={opened} />
)
and the child component (class):
componentDidUpdate(prevProps) {
if(this.props.sidebarOpened !== prevProps){
this.setState({ sidebar: true});
}
}
It's just not working, the child component isn't receiving changes, I guess I didn't pass the prop rightly. I know the code just needs correction but I don't know where I'm not getting it.
Thank you.
The argument to useState is only used once. You need to set the state in openSidenav function to trigger a re-render.
Parent
function openSidenav(){
setOpened(prev => !prev);
}
const [sidebarOpened, setOpened ] = useState(false);
return (
<Sidebar sidebarOpened={sidebarOpened} />
)
Also in child component, use prevProps.sidebarOpened (not just prevProps).
Child
componentDidUpdate(prevProps) {
if(this.props.sidebarOpened !== prevProps.sidebarOpened){ //<---- see here
this.setState({ sidebar: this.props.sidebarOpened});//<---- see here
}
}
p.s - Also, since you are using props directly in child component, you can consider to not to copy props into state.
In parent,
const [sidebarOpened, setOpened ] = useState(false);
function openSidenav(){
setOpened(!sidebarOpened);
}
return (
<Sidebar sidebarOpened={sidebarOpened} />
)
And in child component class directly use this.props.sidebarOpened instead of copying over the prop to state. If you intend to edit the value of sidebarOpened in the child component, pass setOpened to the child component as a prop and use that to edit the value.

Sending props or changing state fo the child won't refresh the Render in ReactJs

I'm new to React, so if someone can please enlighten me. I have a component parent and a component child. I do some data fetching on father, and then I pass the new result through props to the child. But the child is not updating on the render. {the number remains the same} I know its working because I used console.log both in the parent and in the child, and the value is perfect, only the render doesn't show it.
The parent component add props "qtd" to the child:
{this.state.products.map((item)=><div> <Card key={item.id} id={item.id} qtd={item.qtd} title={item.title} addCardCount={this.addCardCount} subtractCardCount={this.subtractCardCount} ref={instance => { this.card = instance; }} /></div> )}
Then, when I add this to the child:
{ this.props.qtd }
Nothing happens on the view.
Even when I change child state through a parent, the view remains the same. Do you know a better way to do this? Do you know the possible causes for this to not work? Appreciate your help.
Try as follow:
In your child component:
class Child from React.Component {
componentDidUpdate(previousProps) {
if (previousProps !== this.props) {
this.forceUpdate()
}
}
render() {
return (<div>{this.props.qtd}</div>)
}
}
You could also use setState() instead for forceUpdate(), and make a conditional render with your state value.

Child component do not see Parent Component state updates

I'm new to React and i'm facing an issue related to state updates.
I have a Parent Component. In the Parent Component constructor, i create multiple instance of a Child Component.
Using the state of the Parent Component, i display one of the Child Component instance.
Instances of Child Component have some Parent Component state value passed as props.
The Parent Component state looks like this (i have simplify the code so it can be clearer)
displayedContainer: {...} // An Instance of Child Component
isLoading: false
The Parent Component constructor looks like this
constructor(props) {
super(props);
// Create state
this.state = {
isLoading: false,
displayedContainer: null
};
// Create all Child Component (Container)
this.defaultComponent = <Container
isLoading={this.state.isLoading}
></Container>
// Others Child Component are created the same way as above.
// To clearify the code i have removed them.
}
And here is the render method
render() {
return (
<div>
{this.state.displayedContainer}
<div className="container-left-bar"></div>
</div>
)
}
From there, i can switch from one Child Component display to another so the state.displayedContainer is working. But when the state.isLoading is getting updated, Child Components doesn't detect it. I think it's because i'm creating the Child Component in the constructor.
How should i do if i want to keep the logic of creating Child Components before rendered it but fix the issue of state updates not detected ?
Thanks for the help !
The problem is that you render the <Container /> only once, in the constructor. The rendered instance is in the memory (this.defaultComponent) and therefore when you call this.setState the child never gets updated - is not notified about the change of any prop. This code should go to render() method.
Think of it like this:
When React determines this.setState (e.g. you want to display other container then the current one), React calls render() method, and should rerender <Container .../> with updated props. But since the code for rendering the component is not in the render() method - code that tells the <Container .../> to use newest isLoading prop from the state, <Container /> never really gets updated with new value of the isLoading prop (or any other prop).
You should achieve something like this:
render() {
...
let renderCurrentContainer = null
if (...) {
renderCurrentContainer = <Container isLoading={this.state.isLoading} ...otherPropsHere... />
}
else if (...) {
renderCurrentContainer = ...
}
else if (...) {
renderCurrentContainer = ...
}
return <...>
{renderCurrentContainer}
</...>
}
If you're asking what to put into the if condition, you need to somehow mark which component to render currently, I'll leave that to your creativity, but you can use something like currentRenderedContainerIndex which can have values {0, 1, 2}, or currentRenderedContainer string from enum e.g. {'FIRST_COMPONENT', 'SECOND_COMPONENT', 'THIRD_COMPONENT'}
Then you would go with something like this:
if (currentRenderedContainer === 'FIRST_COMPONENT') {
renderCurrentContainer = <Container isLoading= {this.state.isLoading} ...otherPropsHere... />
}
else if (currentRenderedContainer === 'SECOND_COMPONENT') {
renderCurrentContainer = ...
}
else if (currentRenderedContainer === 'THIRD_COMPONENT') {
renderCurrentContainer = ...
}

React Native: make API request, set State, and THEN render?

Sorry if this is obvious somewhere in the documentation, but I am trying to wait until my state is set on a parent component before rendering a child component:
Paraphrasing:
class Parent extends Component {
componentWillMount() {
firestack.database.ref()
.then((snapshot) => {
this.setState({myVal: snapshot.val})
})
}
render() {
// Renders before request finishes setting state,
// Child component receives an undefined val
return (
<ChildComponent
myVal={this.state.myVal}
/>
)
}
}
My render hits before the request finishes, so I'm not able to pass the new State to the child component's constructor. How can I properly do this?
Hopefully this is low hanging fruit to someone.
First, I recommend moving your async request to componentDidMount. Not mandatory, but it's a better point in the life-cycle. Your component will need to able to handle myVal == undefined anyway.
Then, don't render the child component until myVal is available:
render() {
return (
<div>
{ this.state.myVal && <ChildComponent myVal={this.state.myVal} /> }
</div>
)
}
Or perhaps, render a spinner instead:
render() {
return (
<div>
{ this.state.myVal
? <ChildComponent myVal={this.state.myVal} />
: <Spinner />
}
</div>
)
}
Note:
You can't use if inside JSX, so this not-so-kosher use of && is required to keep the syntax compact. A common pattern.
You can have a this.state.isReady boolean instead of asking for the presence of a specific value. Another common pattern.
If ChildComponent is the only element you're going to render, you may not need the <div /> wrapper, but you usually do for some reason or other.
If the myVal parameter is a required for ChildComponent, you just need to do this in parent:
return (
{this.state.myVal !== undefined &&
<ChildComponent
myVal={this.state.myVal}
/>
}
)
Then react will render empty string at first time and after changing state it will render again with filled myVal value.
Also, as it was mentioned in another answer, it's much better to use componentDidMount.
It's recommend to do your API calls within componentDidMount rather than componentWillMount. https://facebook.github.io/react/docs/react-component.html#componentdidmount
Setting state in this method will trigger a re-rendering.

react dynamically override child render

I have the following reactJS component structure
<Parent>
<Child1/>
</Parent>
<Parent>
<Child2/>
</Parent>
the children have a function that performs different API calls.. Until thats finished, the child is not ready to be rendered. So is there a way for me to have the parent display
"waiting for data..."
and call the method in the child to do the API call
in the child i would like to have a simple render method which does not have to check if the get API call has completed or not
I have tried two approaches but both unsuccessful
Try call a method in the child with out rendering it.. React.Children.map(this.props.children, (child)=>child.doAPICall()) but this child does not seem to have its functions available
Override the render function dynamically so it renders nothing, then after the children have completed the API calls to swap the render mthod back
React.Children.map(this.props.children, (item, i) =>
(React.cloneElement(item, {
render: () => false
})))
this will allow me to override props but not the render method
Any advice would be greatly appriciated
You should do a conditional render. I would suggest in one of the following two ways:
Either in the parent component. Do the necessary API calls to seed the data, and then render the children when the data is ready.
Or in the children. Do the data calls (for example in componentDidMount), and then render the data when it's ready. Until then render something else, ie some text or an image that says 'Loading'.
Whether or not you decide to conditionally render the children from the parent, or if you simply decide to do a conditional render within the children's render themselves, it would look something like this:
render() {
return (
<div>
{this.state.data?
<div>{this.state.data.somedata}</div>
:
<div>Loading...</div>}
</div>
);
}
or even:
render() {
if (!this.state.data) {
return <div>Loading...</div>
}
return (
<div>
<div>{this.state.data.somedata}</div>
</div>
)
}
Finally, an even more concise way to conditionally render is with this syntax:
render() {
return(
<div>
{this.state.data && <div>{this.state.data.someField}</div>}
</div>
);
}
or for example
render() {
return this.state.data && <div>{this.state.data.someField}</div>;
}
.. hopefully you get the idea :)

Resources