React onChange not working as intended - reactjs

I'm trying to create a products component that get's all the products available on the website and displays each of them in sort of like a box and when the user clicks that box they get redirected to that product page. I'm using react and redux and I'm having a difficulty with onClick. This is how my code looks
class Products extends Component{
constructor(){
super();
this.state = {
products: [...Some array]
};
}
handleProductRedirect(productNumber){
console.log(productNumber)
// Redux function
// this.props.handleRedirect(productNumber)
}
render(){
var products = this.state.products
return (
<div id="content">
{product &&
<div id="searchContent">
{product.map(element => <Item element={element}
handleProductRedirect={this.handleProductRedirect.bind(this)}
key={element['productNumber']}/>)}
</div>
}
</div>
</div>
)
}
};
class Item extends Component{
render(){
var element = this.props.element;
return (
<div id="itemBox" onClick={this.props.handleProductRedirect(element['productNumber'])}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
)
}
}
so the component gets the products from an api and once it's get them it iterates through them. However, I noticed using chromes developer console that every that it iterates through every <Item /> component it calls handleProductRedirect even though that Item wasn't clicked on. It does it automatically. Instead of calling that function when the div itemBox is clicked on, it calls it when it's rendered. Any suggestions

That's because you are calling the handleProductRedirect on every render for each item. Instead of that, you need send the callback in the onClick prop, something like this:
class Item extends Component{
onClickItem = () => { // <=== Defines the callback and bind it to the instance
const { element } = this.props;
this.props.handleProductRedirect(element['productNumber']);
};
render(){
var element = this.props.element;
return (
<div id="itemBox" onClick={this.onClickItem}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
)
}
}
This way you are not calling the callback on every render, but when the user actually clicks element.
Also, don't forget to define the propTypes on your components, it really helps to catch issues later on.

Your onClick is calling the function here:
onClick={this.props.handleProductRedirect(element['productNumber'])}>
Instead you should return a function that calls it with the argument. You can do that by making an arrow function like this:
onClick={() => this.props.handleProductRedirect(element['productNumber'])}>
But the best way to do it is to extract it into a class method (so that you don't get unnecessary re-renders):
class Item extends Component {
clickProduct = () => {
this.props.handleProductRedirect(this.props.element['productNumber']);
}
render() {
var element = this.props.element;
return (
<div id="itemBox" onClick={this.clickProduct}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
);
}
}

Related

Running a Array of Objects outside render function

Either fails to compile defining variables inside componentDidMount. I did a bunch of dozens of other ways. None seems to work for my particular piece of code. I think reading is better than trying to explain.
import React from 'react';
import { connect } from './api';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
giphy: []
}
}
componentDidMount(){
connect(message => {
this.setState({
giphy: message
})
});
var Items = this.state.giphy.map(function(gif){ // items is not defined.
return <li>{gif}</li>;
})
}
render () {
return (
<div className=".App-logo">
<ul>
{ Items } // I wanted to show all items inside the array of objects.
</ul>
<ul className=".App-logo">
// the following method works. We need to make sure to check for this conditions or wont work
{this.state.giphy && this.state.giphy.length > 0 &&
<img src={ this.state.giphy[2].images.original.url}
alt="giphy.com animations"/>}
</ul>
</div>
)
}
}
If I remove the items, it will show the 2nd item in the state.
Can you help to show all in the state?
Instead of creating a variable in componentDidMount which cannot be used inside of render method, you can directly map your state in render method.
<ul>
//This will show only `bitly_gif_url`
{Array.isArray(this.state.giphy) && this.state.giphy.map(gif => <li>{gif.bitly_gif_url}</li>) }
</ul>
Note: Your giphy array contains number of objects. From each object I have shown only bitly_gif_url using {gif.bitly_gif_url}, if you need to show any other item from your object you can change it's key.
You can show mutltiple item's at a time also,
<ul>
//This will show `bitly_gif_url` and `embed_url` at a time
{Array.isArray(this.state.giphy) && this.state.giphy.map(gif => <li>{gif.bitly_gif_url} {gif.embed_url}</li>) }
</ul>
As you have defined Items inside your componentDidMount function it have a functional scope and will not be available inside render function what you can do is return the items from a function. So now your code will look something like
import React from 'react';
import { connect } from './api';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
giphy: []
}
}
componentDidMount(){
connect(message => {
this.setState({
giphy: message
})
});
}
getItems() {
return this.state.giphy.map(function(gif){
return <li>{gif}</li>;
})
}
render () {
return (
<div className=".App-logo">
<ul>
{ this.getItems() } // I wanted to show all items inside the array of objects.
</ul>
<ul className=".App-logo">
// the following method works. We need to make sure to check for this conditions or wont work
{this.state.giphy && this.state.giphy.length > 0 &&
<img src={ this.state.giphy[2].images.original.url}
alt="giphy.com animations"/>}
</ul>
</div>
)
}
}

How to correctly initialize a function in React?

tell me, please, how to solve the following problem correctly?
I have a certain component, there is a control above, when I click on it, setState is triggered. I need to call the function this.setScrollLeft () in which I set to the selected node (ref) in this case the cleavage position.
Here is my implementation, but I am sure that there is a better solution:
import React from 'react';
import { ScoreCell, getScoreTheme } from 'components/scores';
class LeaderboardPlayerResult extends React.Component {
constructor(props) {
super(props);
this.containerWidth = 198;
this.data = this.props.data;
this.playerResultRef = React.createRef();
}
componentDidMount() {
this.element = this.playerResultRef.current;
this.element.scrollLeft = this.containerWidth;
}
setScrollLeft = () => {
if (this.element) {
this.element.scrollLeft = this.containerWidth;
}
};
playerResult = () => {
if (this.data.playOffHoles) {
return (
this.data.playOffHoles.map((item, index) => {
return (
<div
className="leaderboard__player-result-row-wrapper"
key={index}
>
<div className="leaderboard__player-result-row">
<div className="leaderboard__player-result-cell">{item.holeId}</div>
</div>
<div className="leaderboard__player-result-row">
<div className="leaderboard__player-result-cell">{item.holePar}</div>
</div>
<div className="leaderboard__player-result-row">
<div className="leaderboard__player-result-cell leaderboard__player-result-cell--score">
<ScoreCell
childCss='tee-times-card__score'
theme={getScoreTheme(item.playOffParScore)}
>{item.playOffParScore}</ScoreCell>
</div>
</div>
</div>
);
})
);
}
};
render() {
console.log('LeaderboardPlayerResult render');
this.setScrollLeft();
return (
<div
className="leaderboard__player-result"
ref={this.playerResultRef}
>
{this.playerResult()}
</div>
);
}
}
The best place to put this.setScrollLeft() is inside the componentDidUpdate method.
You are already calling this method (this.setScrollLeft()) inside componentDidMount, what is right. Now, you could put another call into componentDidUpdate and it will work pretty much as it is working by now because componentDidUpdate is called before render.
The final outcome will be the same, however, you are separating the concerns: render only render the components and the other methods deal with your business logic.
If you are not sure about componentDidMount and componentDidUpdate, see these excerpts from the official React.js documentation:
componentDidMount()
componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.
componentDidUpdate()
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.

How do I implement an onClick method in one child component that updates the text in a sibling component, based on the state in App.js?

Every row in my SideMenuContainer corresponds to an object from schema.json, showing only the name property. The behavior I want is that when a row is clicked, the PaneViewContainer toggles to display the name and other properties of that respective object from the json.
In App.js, the data is passed to SideMenuContainer like so:
render() {
return (
<MainContainer>
<SideMenuContainer genInfoList={this.state.genInfoList}/>
<PaneViewContainer genInfoList={this.state.genInfoList}/>
</MainContainer>
);
}
In SideMenuContainer, every row is populated like this:
render() {
return (
<SideMenuPane>
<SideMenu>
<div>
<h2>GenInfo</h2>
{this.props.genInfoList.map(genInfoElement => {
return (
<SideMenuRow>
{genInfoElement.name}
</SideMenuRow>
);
})}
</div>
</SideMenu>
</SideMenuPane>
);
}
What I want to do is change the genInfoList information being displayed in the PaneViewContainer based on which row is clicked in its sibling, SideMenuContainer.
The entire genInfoList data is being passed to both sibling components from their parent App.js, so I want to change which portion of that data is loaded in the Pane based on the row clicked in the SideMenu.
I thought about using the Context API, but I'm having trouble figuring out how to implement it for this purpose. Any ideas?
If I understand correctly you have your information stored in the parent element of both components then you can just pass a function down as a prop and have all of your logic stored in the parent element.
changeInfoList = id => {
//change your info list based on id or index or whatever
this.setState({
//your new list
})
}
render() {
return (
<MainContainer>
<SideMenuContainer changeInfoList={this.changeInfoList} genInfoList={this.state.genInfoList}/>
<PaneViewContainer genInfoList={this.state.genInfoList}/>
</MainContainer>
);
}
and then call changeInfoList from your component with props
render() {
return (
<SideMenuPane>
<SideMenu>
<div>
<h2>GenInfo</h2>
{this.props.genInfoList.map(genInfoElement => {
return (
<SideMenuRow>
{genInfoElement.name}
<button onClick={this.props.changeInfoList(genInfoElement.id)>CLick Me</button>
</SideMenuRow>
);
})}
</div>
</SideMenu>
</SideMenuPane>
);
}
this is commonplace in react as you should have smart components and dumb components. When you have components not in the same tree or spread far away then the context api is very useful. In your case I don't think its necessary.
Without external state management, you would have to pass down a callback (as props), so the children can update the parent's state.
As the components get far away from each other, this pattern can get annoying (passing down callbacks each time). That's where external state management can help.
Here's a simple (and untested) example using a callback:
class Counter extends React.Component {
constructor() {
super();
this.increment = this.increment.bind(this);
this.state = {count: 0};
}
increment() {
let count = thist.state.count;
this.setState({count: count + 1});
}
render() {
return <div>
<CounterButton increment={this.increment}/>
<CounterDisplay count={this.state.count}/>
</div>;
}
}
class CounterButton extends React.Component {
render() {
let increment = this.props.increment;
return <button onClick={increment}>Plus One</button>;
}
}
class CounterDisplay extends React.Component {
render() {
let count = this.props.count;
return <span>{count}</span>;
}
}

React+redux get data from child component

I have creator, I mean step1 -next-> step2 -next-> ...
In my parent component I have buttons preview and next, steps content are render as child.
class MyCreator extends React.Component {
render() {
return (
<div>
{this.renderStep(this.props.step.id)}
</div>
<div>
<button>back</button>
<button>next</button>
</div>
);
}
}
In a step I have a component which has only two methods: getData, setData. This is a third party component (so I cannot change implementation).
When I click button next I want to getData from the current step. I mean call some generic method on each step child component, like leaveStep. Then leaveStep returns some data, which I will pass to redux action.
If I got it right, the ideal solution would be lifting state up to the Parent component, take a look at this part of the React documentation. But since you don't have control of your components and it may create you some problems to sync the states. Something like this will do the trick:
class Parent extends Component {
state = {
childData: null
}
getChildData = (data) => {
this.setState({
childData: data,
}, () => { console.log(this.state); });
}
render() {
return <Child setData={this.getChildData} />
}
}
class Child extends Component {
state = {
data: 'this is child data'
}
render() {
return <button onClick={() => this.props.setData(this.state.data)}>Next</button>;
}
}
But remember that this creates a duplicate state, breaking the single source of truth and can be very messy for large applications.

Get the object clicked from the child component in React

I'd like to trigger the function handleDisplayProduct on click and pass to it the object clicked. So far it calls the function handleDisplayProduct when the list is generated for all the objects but the function is not triggered on the click event.
So how do i bind the event onclick with the Container and passing to it the element clicked?
Container
// Method to retrieve state from Stores
function getAllProductState(){
return {
products: ProductStore.getProducts(),
};
}
export default class ProductAllContainer extends Component {
constructor(props){
super(props);
this.state = getAllProductState();
}
handleDisplayProduct(data){
console.log(data);
// ProductActions.selectProduct(data)
}
render(){
const products = this.state.products;
return(
<div>
{ products.map(function(product,i){
return (
<ProductThumbnail
product = { product }
key = { i }
**onDisplayProduct = { this.handleDisplayProduct(product) }**
/>
)
},this)}
</div>
)
}
}
View
const ProductThumbnail = (props)=>{
return(
<div>
<LinksTo to="/" **onClick={props.onDisplayProduct}**>
<h1>{props.product.headline}</h1>
<img src={props.product.images[0].imagesUrls.entry[1].url} alt="Thumbnail small pic"/>
</LinksTo>
</div>
)
}
You need to bind the event listener to the react class. You can do it this way.
constructor(props){
super(props);
this.state = getAllProductState();
this.handleDisplayProduct = this.handleDisplayProduct.bind(this);
}
or alternatively using es6, you can use an arrow function instead.
handleDisplayProduct = (data) => {
console.log(data);
// ProductActions.selectProduct(data)
}
Note: Class properties are not yet part of current JavaScript standard. So the second example wouldn't work unless you add a babel-plugin-transform-class-properties babel plugin
Edit: Also #ryanjduffy pointed out a crucial mistake in your code. Refer his comment.

Resources