Having Trouble Rendering an Object Passed Down in Props - javascript

Hey Guys,
this is my first post on Stack Overflow, but I will do my best to adhere to the proper formats! To sum it up, I have spent the last five hours on this react component, trying to render a list of objects being passed down from the state of my parent component, through the child's props. I have tried every which way, researched multiple solutions, but nothing is seeming to work. This is my first time using webpack, maybe there is some plugin I have to download for using React state and props? I dunno. Anyways, I will post snippets below and describe each step to the best of my ability.
My Parent App
I have had no trouble on this end, I have logged my results every step of the way so I know for a fact that the getWeather function is properly fetching data from the Weatherbit API and setting the state, which in turn as you will see, passes the array to the child via it's props.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {weatherArray: []}
this.getWeather = this.getWeather.bind(this);
}
getWeather() {
const newWeather = WeatherHelper.inputPrompt();
console.log(newWeather)
this.setState({weatherArray: newWeather})
WeatherHelper.showData()
}
Still on My Parent App
As you can see, I pass the state as a prop to the child, which would update soon as setState runs
<div id='side-apps'>
<div id='weather-side-app'>
<WeatherApp weatherArray={this.state.weatherArray} />
</div>
</div>
My Child App
So this is where it gets weird...
This is my child component, and as you can see when the component receives new props, it first checks whether or not those props are updated. Then, assuming those props are new, the componentWillReceiveProps method will set the state and re-render accordingly (or so you would think). You can also see that I have extensively used logging to try to document my problem, which I will provide a screen shot of next. Notice that in the componentWillReceiveProps method, if the props are different than the originals (i.e. updated) the method will log multiple dashes around the updated props so you can point them out (you'll see). By the way, I understand that I shouldn't even have to use the componentWillReceiveProps method, the parents setState function alone should have been enough to update my component, but alas it did not (and neither did the other 10 different ways I tried).
class WeatherApp extends React.Component {
constructor(props) {
super(props);
this.state = {currentWeather: []}
}
handleCloseClick() {
WeatherHelper.hideData();
}
componentWillReceiveProps(nextProps) {
if (nextProps.weatherArray !== this.state.currentWeather) {
console.log('---')
console.log(nextProps.weatherArray)
console.log('---')
this.setState({currentWeather: nextProps.weatherArray})
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's
currentWeather array's length" +
" as zero. EVEN THOUGH the componentWillReceiveProps method" +
" updated and set the state accordingly...")}
console.log(nextProps.weatherArray)
console.log(this.state.currentWeather)
}
}
render() {
if (this.state.currentWeather.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
<h2>{this.state.currentWeather}</h2>
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log(this.state.currentWeather)}
return <p></p>
}
}
}
Child Component Logging Details
if you look the the screen shot, you can see that my logging from my child's componentWillReceiveProps method proves that the setState function must have been called, but for some reason it does not update the child's html and display data.
child component logging details
Child Component React Details
Okay last thing... I also am posting the react chrome dev tool that shows you what components have props and states. You can see in this image, that the child component did receive the intended props and updated it's state accordingly. But again, for some reason it will not allow me to access them and display results
child component react details
I really apologize if this question is too lengthy... And yes, I understand that there are multiple similar posts already solved, but I have looked at most of them, and my situation just does not apply from what I can tell... I am not an expert, but I am no newbie to react. This has really stumped me. Thank you so much in advance for your help. I can provide additional screenshots, code snippets, or even videos if that helps!!!
makezi
EDIT
I added replace the block of code with a map function, and as you can see, the problem is not how I am delivering the data to the DOM. The problem is for some reason, my component is not reading the data in it's state (I will post a more detailed screenshot to better document the problem)
render() {
console.log('++++')
console.log(this.state.currentWeather.length);
if (this.state.currentWeather.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
{this.state.currentWeather.map(item => <div><span>{item.city}</span></div>)}
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's currentWeather array's length" +
" as zero. EVEN THOUGH the componentWillReceiveProps method" +
" updated and set the state accordingly...")}
{console.log(this.state.currentWeather)}
return <p></p>
}
}
better logging details
Second Edit for Minh Hưng Trần's Comment
I updated my parent's getWeather function to be asynchronous, also edited logging to be more descriptive. You can see even though the currentWeather array in state has 8 objects, the length is still being read as zero! I don't think the problem is comparing the arrays. For some reason, my render function cannot read the data in my state... or it is not being updated when setState is ran. **you cannot tell from the screenshot, but the nextProps array is read and fully logged to console. although after running setState, the ''console.log(this.state.currentWeather)'' logged an empty array, weird right? **
componentWillReceiveProps(nextProps) {
if (nextProps.weatherArray !== this.state.currentWeather) {
console.log("--nextProps weatherArray--")
console.log(nextProps.weatherArray)
console.log('--------------------------')
this.setState({currentWeather: nextProps.weatherArray})
console.log("==child's state==")
console.log(this.state.currentWeather)
console.log("=================")
} else {
console.log(nextProps.weatherArray)
console.log(this.state.currentWeather)
}
}
render() {
console.log("++child's state's length in render method++")
console.log(this.state.currentWeather.length);
console.log("+++++++++++++++++++++++++++++++++++++++++++")
if (this.state.currentWeather.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
{this.state.currentWeather.map(item => <div><span>{item.city}</span></div>)}
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's currentWeather array's length" +
" as zero. EVEN THOUGH the componentWillReceiveProps method" +
" updated and set the state accordingly...")}
{console.log(this.state.currentWeather)}
return <p></p>
}
}
better logging details
To reiterate, in my componentwillreceiveprops function: the "console.log('nextProps.weatherArray)" DOES emit the full array of data... HOWEVER, the "console.log('this.state.currentWeather)" does NOT. Even after setState is run. But then when I use the React Chrome Dev Tools, you can see that the state DID in face get passed to the child component. My render method just cannot read the data for some reason..
THIRD EDIT
This first snippet is my parent component. You can see that it is passing the weatherArray to the child's props. This getWeather function DOES work by the way, I have been validating it through logging. The problem is on my child's end,I am sure of it.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {weatherArray: []}
this.getWeather = this.getWeather.bind(this);
}
async getWeather() {
const newWeather = await WeatherHelper.inputPrompt();
console.log(newWeather)
this.setState({weatherArray: newWeather})
WeatherHelper.showData()
}
render() {
return (
<div>
<div id='main'>
<div id='introduction'>
<Intro />
</div>
<div id='attachment'>
<Attachment getWeather={this.getWeather} />
</div>
<div id='body'>
<div id='about'>
<About />
</div>
<div id='personal-info-skills'>
<div id='info'>
<p id='info-header'>personal information</p>
<Info />
</div>
<div id='pskills'>
<p id='pskills-header'>personal skills</p>
<PSkills />
</div>
</div>
<div className='divider'>
<p></p>
</div>
<div id='professions-languages'>
<div id='professions'>
<p id='professions-header'>professions</p>
<Professions />
</div>
<div id='languages'>
<p id='languages-header'>languages</p>
<Languages />
</div>
</div>
</div>
</div>
<div id='side-apps'>
<div id='weather-side-app'>
<WeatherApp weatherArray={this.state.weatherArray} />
</div>
</div>
</div>
)
}
}
Now I will post my child's code snippet after commenting out componentWillReceiveProps. Instead of trying to display data in my child's state, now I am simply trying to use props data that was passed from the child's parent component. Unfortunately, I am still running into the same problem. My child's render function is reading the props array as empty, and therefore cannot display data. I can't take away that if statement at the beginning of the render method because then my code throws errors when it tries to read attributes of an empty array
render() {
if (this.props.weatherArray.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
{this.props.weatherArray.map(item => <div><span>{item.city}</span></div>)}
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's currentWeather array's length" +
" as zero. EVEN THOUGH the React Chrome Dev Tools shows that" +
" the child has a full currentWeather array in props...")}
{console.log(this.state.currentWeather)}
return <p></p>
}
}
}
before running getWeather function screen shot
after running getWeather function screen shot
Look at the two above screenshots. The first one is before I run my fetch function, so the React Chrome Dev Tools show my child's props array is empty (of course). BUT the after screen shot shows that even though the React Dev Tools show my child component's props has a full array, my render method still reads that prop array's length as 0....

React documentation suggests avoiding legacy lifecycles such as componentWillReceiveProps.
A common misconception is that getDerivedStateFromProps and componentWillReceiveProps are only called when props “change”. These lifecycles are called any time a parent component rerenders, regardless of whether the props are “different” from before. Because of this, it has always been unsafe to unconditionally override state using either of these lifecycles. Doing so will cause state updates to be lost.
Documentation says:
componentDidMount() is invoked immediately after a component is mounted... If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
EDIT:
Looking at the third edit, I've been assuming that you were fetching calls through the componentDidMount lifecycle, since you kept iterating that the fetching 'works'.
Since the snippets above do not actually show a componentDidMount method, I suggest trying to move all fetching logic to the appropriate componentDidMount method then setting state from there.

First, the getWeather function is fetching Weatherbit API, so you must use await
on fetch function. In your code above, I think you use function WeatherHelper.inputPrompt() to fetch data, so the function will be:
async getWeather() {
const newWeather = await WeatherHelper.inputPrompt();
console.log(newWeather)
this.setState({weatherArray: newWeather})
WeatherHelper.showData()
}
In your code above, console.log was print data because it can print data asynchronous (according to my experience).
Second, you shouldn't compare array with array by !==, read this.
Please change your code, if not working, please log data of nextProps and state of Child component in componentWillReceiveProps and show me the screenshot of it.
Sorry my english very bad.

You don’t need to use componentWillReceiveProps. This is adding a lot of unnecessary complexity for you. When the state changes in the parent component, it automatically re-renders its children. Omit the componentWillReceiveProps and render in the child component using this.props.weatherArray.

Related

Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. (React-js)

What i want is when i click on button the React component should re-render
And i also Don't want to re-Render the complete webiste on this particular component has to re-render
It tried using hooks to re-render the page but it gives me error as shown above and also tried using forceUpdate() Hook but that still gives me the same error
re-Render the component
This is the javascript of same file
const LeftCard = ({ cardData: cardComponent, getFileName }) => {
let emptyArr=[];
let pageNo=10;
const [loadComponentByPage,setPageArr]=useState(undefined);
if(pageNo){setPageArr(emptyArr);console.log(emptyArr)}
pageNo<10?pageNo=10:''
pageNo===10 ? pageChange(0):null
function pageChange(num){ <-------- This function is getting called on button click
if(cardComponent !==undefined) {
const mainArray=cardComponent.sort((a,b)=>b.id-a.id).map(val=>{return val});
const arrayLength=cardComponent.length;
if(pageNo===10 && num===-10)return;
if(arrayLength<pageNo && num ===10)return;
pageNo+=num
emptyArr=[]
for(let i=pageNo-10;i<=pageNo;i++){
if(mainArray[i]!==undefined){
emptyArr.push( mainArray[i])
}
}
}
}
if(loadComponentByPage ===undefined )return
This is jsx of same file
return (
<div>
{loadComponentByPage.sort((a, b) => (a.id - b.id))
.map((element, i) => {
return (
<div key={i}>
<span className="image-overflow flex ">
<img className="left-sec-image ml2 link Dim" src={element.image} alt={element.topicname}></img>
<div>
<h4 className=" ml4 mr2 light-purple">{element.topicname}</h4>
</div>
</span>
</div>
);
}).reverse()}
<div>
<div className='ml5'>
These are the button that calling the function-------> <button className='ml7' onClick={()=>{pageChange(-10);doNothing()}}><ArrowBackIosSharpIcon /></button>
These are the button that calling the function-------> <button className='ml6' onClick={()=>{pageChange(10);doNothing()}}><ArrowForwardIosSharpIcon /></button>
</div>
</div>
</div>
)};
export default LeftCard;
The problem you are having is that you've created an infinite loop of renders.
It's a common pitfall in React and is something to look out for.
In React, a component re-renders whenever one of these 2 cases occurs:
Whenever there is a change in the component's local state.
Whenever any of it's parent components themselves are re-rendered.
So what happens is the following:
When the component renders, the value of pageNo is 10, which is "truthy".
This means that we enter the if block and setPageArr is called, thus setting the component's state.
The component is re-rendered due to the state update.
The value of pageNo is still 10, so we enter the if block and set the state again.
And this cycle repeats, creating an infinite loop.
Typically, when faced with an infinite loop, the browser would freeze and ultimately crash.
The error you are getting is due to React "catching" the problem and preventing the freeze.
To fix it we have 2 options:
Since in this case we already know the initial value of the state is an empty array, we can simply initialize it as such.
Example:
const [loadComponentByPage,setPageArr]=useState([]);
If we don't know the initial value, such as the case with values fetched from an external API / etc.. We can utilize the useEffect hook.
Example:
useEffect(() => {
setPageArr(emptyArr)
}, []) // A `useEffect` hook with an empty dependency array should run only once when the component mounts.
Either way, this bit needs to go -> if(pageNo){setPageArr(emptyArr);console.log(emptyArr)}

useState element not rerendering when triggered by an event handler in a child component

I have a parent component with a useState that is being displayed. What I want to do is have a child component be able to update this state to change what the parent is displaying. I currently have the following:
function Parent() {
const [myWindow, setMyWindow] = useState(null)
useEffect(() => {
setMyWindow(<Child updateWindowFunc={() => setMyWindow(someNewWindow)} />)
}, []}
return (
<div>
{myWindow}
</div>
)
}
function Child({updateWindowFunc}) {
//Somehow calls updateWindowFunc
}
(Note that my actual code is set up somewhat differently, so don't mind the syntax as much as the general concept.)
I've found that I can get the value of myWindow to change, but the actual display doesn't show any change. I've tried forcing a re-render after the change, and adding a useRef to display useRef.current, but nothing seems to update which window is actually being rendered.
What am I doing wrong?
Edit:
I've found that it works if I switch to a different type of component, but if its just a different element of the same component then there is no re-render. I've been using React.createElement(), so I would think the 'objects' are distinct, but maybe I just misunderstand how this works.
Can you provide more details or working\not working code example that has more info?
Currently it's hard to get, cause that doesn't make any sense since you can just render it like below and that code will have same result as one that you are trying to run
function Parent() {
return (
<div>
<Child />
</div>
)
}
So for better understanding and fixing a problem \ finding a different approach, please, provide more details

Conditional Rendering in JSX not hiding the Element

So i have this react component which contains a conditional rendering in some part. so far so good and this practice has been acting as expected all throughout my app. but surprisingly the element we're talking is not getting hidden as a result of change in condition.let me provide you with a minimal representation of relevant code because the original component is too lengthy and cumbersome.
import React from 'react';
import AnotherComponent from '../components/AnotherComponent';
export class TheComponent extends Component {
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === 'AAA';
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent(this.props.data) && <AnotherComponent />}
</div>
</div>
);
}
}
export default TheComponent;
Unfortunately creating a fully working sample is a bit hard, considering all the third party dependencies and the redux wiring. Nevertheless, if you have ever run into similar situation and got to the bottom of it, please share your experience with me.
note: Updated props.data is passed to the component normally. in the react dev tools it shows up and in redux dev tools history of state is quite healthy. the only problem here is that the conditional won't hide the element in a falsy state.
UPDATE. the reason for this weird rendering was a dynamic loop in the same component rendering this AnotherComponent regardless of the flag value. what made it hard to pin down was that it was rendering it in map and passing index of dynamic string content. anyhow, thank you all and sorry for the possible misleading question.
It is working fine here.
You should check the data in your props if there is any flag key or not and if there is a flag key check that if it is AAA or not.
class App extends React.Component {
constructor() {
super();
this.state = {
data: "aa"
};
}
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === "AAA";
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent({ flag: this.state.data }) && (
<div>asdsdasd</div>
)}
</div>
<button onClick={() => this.setState({ data: "AAA" })}>Toggle</button>
</div>
);
}
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Click on Toggle and the Data will be displayed.

How to handle Ajax in pure function components in react?

I'm trying to write all my components as pure functions, but now I run into trouble. I have a component that looks somewhat like below. Trouble is, the result of the ajax request causes a rerender, which causes another ajax request, and there you have your infinite loop. How to properly handle this?
const PageProductFunnel = function (props) {
const agent = ajaxagent;
var list = [];
agent.get(`https://mylist.com/${productSKU}`).then((res) => {
list = res.data;
});
return (
<div>
<div className="cell">
<article className="article">
<h1 className="article-title">{product.name}</h1>
<FunnelStep {...props} list={list} />
<ButtonAddToCart product={product} />
</article>
</div>
</div>
);
};
There are few approaches you can take:
fetch async data globaly, not inside render of the component
Do not use pure function for this component, and hook async into life cycle methods
You need to use stateful components for this.
class PageProductFunnel extends React.Component {
state = {
"list": []
}
componentWillMount () {
agent.get(`https://mylist.com/${productSKU}`).then((res) => {
this.setState({list:res.data})
});
}
render () {
return (
<div>
<div className="cell">
<article className="article">
<h1 className="article-title">{product.name}</h1>
<FunnelStep {...props} list={this.state.list} />
<ButtonAddToCart product={product} />
</article>
</div>
</div>
);
}
};
EDIT Please read the comments. After some more considerations, I've decided to implement Davorin Ruševljans first suggestion. My own solution works but his is better.
Thanks for the suggestions but they really don't solve the problem. All my code is pure so far, and I really want to stick with it. Making the call on a global level doesn't change anything in my case as the response would still cause a render.
I changed the code so that the list is part of the state (I'm using redux). Only if the list is empty, I perform the ajax call. Whenever I know I expect new data, I clear the old list before the page renders again.
if (props.list.length === 0) {
agent.get(`https://mylist.com/${productSKU}`).then((res) => {
props.setList(res.data);
});
}
The better life cycle hook to do an ajax call is componentDidMount(). If you are so specific to use pure component you can use in pure Component as well.
class Sample extends PureComponent{
//react implement ShouldComponentUpdate for us which does shallow comparison
componentDidMount(){
//api call here
}
}
But You can't build a react app using only pureComponent, Because if there is a case where you wan't to stop the updation life cycle by checking the state, then you can't do that using PureComponent Because Pure component does only shallow checking for us and it checks for all the states in the component.
We should balance stateful, PureComponent, Stateless among the project.

React: update component's props from outside of render method without using state

Here is what I'm trying to achieve. I have two React components Product and ProductInfoPanel, shown inside a ProductList component. Product displays selected information about a product, such as product name, and price. When a product is clicked, more details will be shown in the ProductInfoPanel. So I need to pass wah twas clicked to the ProductInfoPanel.
Here is how I currently wire them up together. Each Product gets a click handler passed in, which passes back the product object when invoked, then that is passed into the ProductInfoPanel's props. The ProductList uses state to keep track of what was clicked, so when it changes, it triggers the re-rendering of the info panel.
class ProductList extends React.Component {
render() {
return (
<div>
<div className='content'>
<ul>
{ this.props.products.map((product, index) => {
return (
<li key={index}>
<Product product={product}
clickHandler={this.onProductClicked.bind(this)}/>
</li>
);
})}
</ul>
</div>
<div className='side-panel'>
<ProductInfoPanel product={this.state.selectedProduct} />
</div>
</div>
);
}
onProductClicked(clickedProduct) {
// Use the product object that was clicked, and updates the state.
// This updates the info panel content.
this.setState({ selectedProduct: clickedProduct });
}
}
Here is roughly how the two components are constructed.
class Product extends React.Component {
render() {
// Even though it needs only name and price, it gets the whole product
// object passed in so that it can pass it to the info panel in the
// click handler.
return (
<div onClick={this.onClicked.bind(this)}>
<span>{this.props.product.name}</span>
<span>{this.props.product.price}</span>
</div>
);
}
onClicked(e) {
this.props.clickHandler(this.props.product);
}
}
class ProductInfoPanel extends React.Component {
render() {
// Info panel displays more information about a product.
return (
<ul>
<li>{this.props.product.name}</li>
<li>{this.props.product.price}</li>
<li>{this.props.product.description}</li>
<li>{this.props.product.rating}</li>
<li>{this.props.product.review}</li>
</ul>
);
}
}
This is the best I could come up with, but using state to keep track of what product was clicked still sounds wrong to me. I mean, it's not really a state of a component, is it?
If I could update props of a referenced React component from outside of the render method, then I'd try to pass a reference to a ProductInfoPanel to each Product, so they could do update it in their click handler.
Is there a way to achieve what I want and avoid using state to keep track of what was clicked?
You could use a flux-like library like redux, or an alternative like mobx to remove state management from your component, but my personal feeling is to keep it as simple as possible until you really feel like there will be significant benefit in adding another layer of abstraction into your project.
I used to start off projects using redux by default but then one time I kicked myself as it turned out that the added complexity of introducing a redux implementation turned out to be overkill for what was actually a fairly small and simple project. I don't know if there is a hard line to know when you should shy away from using standard state and introduce another library to manage it for you, but I have learned that it's probably safest to do it the easiest and simplest way first until you genuinely feel there is actual benefit in bring in another dependency.
A few bits of advice on your current code...
You are binding your functions in the properties like so:
<Product product={product} clickHandler={this.onProductClicked.bind(this)}/>
When you call function bind it actually returns a new function instance, therefore React's reconciler will see it as a new prop coming into your component and will therefore always re-render the subcomponent tree. Something to be aware of. As an alternative approach you can do early binding in your constructor like so:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.onProductClicked = this.onProductClicked.bind(this);
}
render() {
...
<li key={index}>
<Product product={product}
clickHandler={this.onProductClicked}/>
</li>
...
}
}
Additionally, where you are providing index as they unique key prop above - you should consider using a unique identifier from your product model (if it's available). That way if you add or remove items from the list React will have more information to know whether or not it should re-render all of the Product component instances.
For example:
render() {
...
{
this.props.products.map((product) =>
<li key={product.id}>
<Product product={product}
clickHandler={this.onProductClicked}/>
</li>
)
}
...
}
Read more about these concepts here:
https://facebook.github.io/react/docs/advanced-performance.html
I think it's fine. If there were more components that responded to changes in SelectedProduct, then the value of having the parent component control the state would be more apparent. In your case, it might not seem necessary, since only a single component changes.
However, if your Product also responded by highlighting the SelectedProduct, and a RecentlyViewedProducts list responded in some way to the SelectedProduct, then it would become evident that the SelectedProduct isn't the state of the ProductInfoPanel, but state of a higher level part of the application that it's an observer of.

Categories

Resources