Parent component re-renders cause duplicate children component renders - reactjs

I have a component with URL like product/:id and it has some children components. When I route and change param id I get data from server again, set state, and re-render the page. But the children components are not re-rendered, they are rendered again ( So now I have twice same HTML Code ).
I don't know why it happened.
This is my children component:
listProductCardHTML = this.state.randomList.map((card, index) => {
return (
<ProductCard cardContent={card} key={index}>
</ProductCard>
)
})
And this is JSX:
<div className="box-product product-carousel" id="related-carousel">
{listProductCardHTML}
</div>

The problem seems that when you get routed to your product/:id, you push a new child to the randomList array.
Therefore the code listProductCardHTML = this.state.randomList.map((card, index) generates multiple JSX elements and are rendered.
Please check you setState method and check if randomList is being set correctly.

Related

React updating prop and rendering based on boolean value

I have a parent function component which has a boolean (which is set via child component and also used to render some container on parent component).
Is the below setup fine in terms of updating and dynamic rendering based on isSomeBoolean?
const [isSomeBoolean, setisSomeBoolean] = useState(true);
const updateIsSomeBoolean = (boolVal) => {
setisSomeBoolean(boolVal);
}
<ChildComp updateIsSomeBoolean={updateIsSomeBoolean} />
{isSomeBoolean && (
<div className="container">
....
</div>
)
}
In the child component, somewhere I invoke the parent function as below;
props.updateIsSomeBoolean(false);
Yes, Passing state and controller function to the child component is very normal in react.
But always keep in mind that changing state in parent component will render both components so keep the state near to component where is it required.
In your scenario, you're going in the right direction.

Adding a component to the render tree via an event handler, the component doesn't seem to receive new props. Why is this?

I have a context provider that I use to store a list of components. These components are rendered to a portal (they render absolutely positioned elements).
const A = ({children}) => {
// [{id: 1, component: () => <div>hi</>}, {}, etc ]
const [items, addItem] = useState([])
return (
<.Provider value={{items, addItem}}>
{children}
{items.map(item => createPortal(<Item />, topLevelDomNode))}
</.Provider>
)
}
Then, when I consume the context provider, I have a button that allows me to add components to the context provider state, which then renders those to the portal. This looks something like this:
const B = () => {
const {data, loading, error} = useMyRequestHook(...)
console.log('data is definitely updating!!', data) // i.e. props is definitely updating!
return (
<.Consumer>
{({addItem}) => (
<Button onClick={() => {
addItem({
id: 9,
// This component renders correctly, but DOESN'T update when data is updated
component: () => (
<SomeComponent
data={data}
/>
)
})
}}>
click to add component
</Button>
)}
</.Consumer>
)
}
Component B logs that the data is updating quite regularly. And when I click the button to add the component to the items list stored as state in the provider, it then renders as it should.
But the components in the items list don't re-render when the data property changes, even though these components receive the data property as props. I have tried using the class constructor with shouldComponentUpdate and the the component is clearly not receiving new props.
Why is this? Am I completely abusing react?
I think the reason is this.
Passing a component is not the same as rendering a component. By passing a component to a parent element, which then renders it, that component is used to render a child of the parent element and NOT the element where the component was defined.
Therefore it will never receive prop updates from where I expected it - where the component was defined. It will instead receive prop updates from where it is rendered (although the data variable is actually not coming from props in this case, which is another problem).
However, because of where it is defined. it IS forming a closure over the the props of where it is defined. That closure results in access to the data property.

ReactJS: Empty component's old fetched data before fetching again

I am fetching remote data from my React component. When data is ready, child components are rendered. While data is loading, the 'Data is loading ....' text is displayed.
When the component is rendered for the second time due to the props change, I set the previous data to null in order to show that new data is loading.
const List = (props) => {
const [items, setItems] = useState(null);
useEffect(() => {
setItems(null);
fetch(`http://some_url_to_fetch_items.com?PAGE=${props.page}`)
.then((data) => {
setItems(data);
})
}, [props.page]);
if (!items) {
return "Data is loading ......."
}
return (
<ul>
{items.map(item => (
<li>{item}</li>
))}
</ul>
)
}
The problem of this approach is that when the component is rendered for the second time the setItems(null); code is not executed immediately (probably because useEffect is executed asynchronously) and the component re-renderes 3 times instead of expected 2:
1-st re-render because of the props change (BUT with old data, since setItems(null) is executed too late)
2-nd re-render after setItems(null) is finally executed
3-rd re-render after data is fetched
I understand what the problem with my approach is. But I don't see another way.
It is mandatory to use hooks.
Any ideas?
A quick fix would be to add a key prop to your List:
<List key={page} page={page} />
When the value for page changes, the List component will be unmounted, and a new one rendered. The new one will not have any previous data in it, so you will only get 2 renders instead of 3.
This means you don't have to set previous data to null anymore.
If you do this, you'll need to check that the component is still mounted in your useEffect before calling setItems, otherwise you'll be trying to set state on an unmounted component. You can write:
if(setItems) {
setItems(data)
}
Changing the key prop is a bit hacky though, so I would investigate other ways to solve your issue.
Maybe you should pass the data into the list instead of the list being responsible for fetching data, and have the page state and data both inside the parent component?
For now though, changing the key using the page should work.
use this method :
useEffect(() => {
fetch(`http://some_url_to_fetch_items.com?PAGE=${props.page}`)
.then((data) => {
setItems(data);
})
}, []);
return (
<ul>
{items? items.map(item => (
<li key={item.id}>{item}</li>
)):"Data is Loading..."}
</ul>
)

Passing portal location down to children

I want to use React Portals (https://reactjs.org/docs/portals.html). The location where a portal should render is also rendered by React. There is a component that renders the portal "placeholder" (so to call it) and must pass it down to its children so that one (or many) of them can ReactDOM.createPortal.
Is there a recommended way for doing so?
I am using refs to capture the portal placeholder. On the first render the children get null in the prop they are expecting. I am also checking if it's null on the children before trying to render the portal. I am NOT storing the ref in the state. Sometimes it seems that acquiring the ref causes a prop change in the children, sometimes it just doesn't work.
If I try to document.getElementById directly on the children (by hardcoding the id of the placeholder in the parent component) it doesn't work because on the first render the DOM doesn't know about that element.
EDIT: Added code example
class Parent extends React.Component {
render {
return (
<div>
<div ref={ref => this.portalContainer = ref}/>
<SomeChild renderYourPortalHere={this.portalContainer}/>
</div>
)
}
}
const SomeChild = props => (
props.renderYourPortalHere
? ReactDOM.createPortal(<IrrelevantOtherComponent/>, props.renderYourPortalHere)
: null;
)

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