Get the object clicked from the child component in React - reactjs

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.

Related

How can I import element to another React component?

I've got 2 components and want to get Panel_Menu element in another child component to do some stuff with it.
class Panel extends Component {
constructor(props){
super(props);
this.menuRef = React.createRef();
}
componentDidMount() {
console.log (this.menuRef.current)
// works correctly
}
render() {
return(
<>
<Panel_Menu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current}/>
</Panel_Menu>
</>
)
}
}
class Menu extends Component {
constructor(props) {
super(props);
}
isSame = () => {
const isSlideClass = this.props.item;
console.log(isSlideClass)
// is null
// expected output: → <div class="panel-menu"></div>
}
render() {
return (
<Left_Menu >
<Panel_Menu_Items className="test" onClick={this.isSame} />
</Left_Menu>
);
}
}
How can I update data in done render() to reach my goal?
Or... how can I get element instantly in external Component (Menu in this case) to do some stuff with it?
Issue
The issue here is that React refs, when attached on the initial render, will be undefined during the initial render. This means that item={this.menuRef.current} will enclose the initial undefined ref value in the click handler of the child.
Solution
It's simple, you really just need to trigger a rerender to reenclose an updated React ref value. You can either add some state to the Panel component and update it in the componentDidMount lifecycle method, or just issue a forced update.
class Panel extends Component {
menuRef = createRef();
componentDidMount() {
console.log(this.menuRef.current);
this.forceUpdate(); // <-- trigger rerender manually
}
render() {
return (
<>
<PanelMenu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current} />
</PanelMenu>
</>
);
}
}
Demo

How do I successfully change the state in one component from another?

I am calling setState in one component in an onClick function in another component but it says this.setState is not a function and that a component is repeatedly calling setState inside componentWill update. How do i fix this?
This is the relevant of the app.js file:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeScreen: 'dashboard'
}
}
setActiveScreen(key) {
this.setState({
activeScreen: key
})
}
render() {
return (
<NavBar activeScreen={this.state.activeScreen} setActiveScreen={this.setActiveScreen} />
)
}
This is the relevant part of the Navbar component:
class NavBar extends Component {
render() {
return (
<li className="nav-item" key={key}>
<Link className={`nav-link ${this.props.activeScreen == i.key ? 'active' : ''}`} to={i.to} onClick={() => {this.props.setActiveScreen(i.key)}}>
{i.label}
</Link>
</li>
)
}
You have to bind the setActiveScreen function to this in the constructor of your App component.
Like this:
constructor(props) {
super(props);
this.state = {
activeScreen: 'dashboard'
}
this.setActiveScreen = this.setActiveScreen.bind(this);
}
Or, as #Nico said in the comments, use an arrow function.
Like this:
setActiveScreen = key => {
// logic
}
If you have this function in a Class based component then you can update your current function to an arrow function like below.
setActiveScreen = (key) => {
this.setState({
activeScreen: key
})
}
The thing is you are accessing this without a context being given.
Another way can be you bind this to the function. Below is the example to do it the other way inside your constructor.
constructor(props) {
super(props);
this.setActiveScreen = this.setActiveScreen.bind(this);
// Other code ....
}
First every methods how use setState has to be bind in the constructor like this :
this.setActiveScreen = this.setActiveScreen.bind(this);
Secondly, just for the good practice define your props in NavBar like this :
static propTypes = {
setActiveScreen: PropTypes.func.isRequired,
}
Thirdly, is not a good practice to use arrow function in your render. So you have to possibility :
Using partial from lodash
so it looks like this :
<Link onClick={ _.partial(this.props.setActiveScreen, i.key)}}>
Or you can define a specific method in your NavBar component called by onClick and you retrieve your key in the event object provide by default by onClick

Add component on button click

I am making a front end application using typescript and react. I have a component A which amongst other html elements has a textbox. I want to add this component A on click of a button. So if the user clicks the button multiple times, i want a new component A to be created on every click. Also I want to be able to store the text data so that I can later fetch it and process it.
I tried to make a list of this component but it gives me an error.
interface State {
componentList?: ComponentA[];
}
export class ComponentList extends React.Component<Props, State> {
  constructor(props) {
    super(props);
    this.onClick = this.onClick.bind(this);
  }
public onClick(event) {
const componentList = this.state.componentList;
this.setState({
componentList: componentList.concat(<ComponentA key=
{componentList.length} />)
});
}
  public render() {
    return (
      <React.Fragment>
        <button onClick={this.onClick}>Add component</button>
{this.state.componentList.map(function(component, index)
{
return ComponentA
})}
      </React.Fragment>
    );
  }
}
You might want to make two changes in your code.
First initialise your state in the constructor,
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = { componentList: [] }
}
So that react can track that data.
Second thing is, you are returning wrong item from the map in the render function.
Try returning component, which is different copies of <ComponentA ../> that you pushed every time you clicked the button,
public render() {
return (
<React.Fragment>
<button onClick={this.onClick}>Add component</button>
{this.state.componentList.map(function(component, index)
{
return component;
})}
</React.Fragment>
);
}
Keep the component count in the state:
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
numComponents: 0
}
}
Add a new function which creates an array of the component for rendering later:
clickedComponents = () => {
let componentArray = [];
for (let i=0; i<this.state.numComponents; i++) {
componentArrays.push(<ComponentA />);
}
return componentArray;
}
Increment the component count with your onClick function:
public onClick(event) {
this.setState({numComponents: this.state.numComponents + 1});
}
Render the component array:
public render() {
return (
<React.Fragment>
<button onClick={this.onClick}>Add component</button>
{this.clickedComponents()}
</React.Fragment>
);
}

React onChange not working as intended

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>
);
}
}

Fetch data periodically from server and re-render view in reactjs

I want to fetch data from server periodically and refresh rows when data is fetched by setState() but the rows doesn't re-render after setState().
constructor(props) {
super(props);
this.state = {
rows: []
}
this.refreshList = this.refreshList.bind(this);
}
refreshList() {
req.get('/data').end(function (error, res) {
// type of res is array of objects
this.setState({
rows: res
});
});
}
// call this method on button clicked
handleClick() {
this.refreshList();
}
render() {
return(
<div>
<button onClick={this.handleClick}>Refresh List</button>
<Table rows={this.state.rows}/>
</div>
);
}
when call refreshList() new feteched data doesn't render.
My table component is:
// Table component
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Thanks a lot for your help. How can I refresh list on click button?
Your table component never changes its state after the construction. You can fix it easily by updating the state from new props:
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
componentWillReceiveProps(newProps) {
this.setState({
rows: newProps.rows
});
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
However, if your table component is so simple, you can make it stateless and use props directly without setState():
export class Table extends Component {
render() {
return (
<div>
{this.props.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Note there is no need for constructor now. We could actually make it a functional component.
Use an arrow function:
req.get('/data').end((error, res)=> {
// type of res is array of objects
this.setState({
rows: res
});
});
With the ES5 style callback function, the context of this gets lost.
You could also bind this directly to a local variable, i.e., var that = this and stick with the function syntax, but I think most would agree what the ES6 arrow syntax is nicer.

Resources