onClick does not work for custom component - reactjs

Why does onClick not work directly on my custom element as shown here? The function chosenResource does not get invoked.
return (
<div>
{
resources.map(resource => {
return (
<SelectListItem key={resource.id} onClick={this.chosenResource.bind(this)} resource={resource} />
)
})
}
</div>
)
However, if I encapsulate my custom element in a div element and have the onClick on the div element then it works.
return (
<div>
{
resources.map(resource => {
return (
<div key={resource.id} onClick={this.chosenResource.bind(this)}>
<SelectListItem resource={resource} />
</div>
)
})
}
</div>
)
What is wrong with the first approach?

#nrgwsth is correct, if you still want to stick with your first approach, you may use:
return (
<div>
{
resources.map(resource => {
return (
<SelectListItem key={resource.id} customClickEvent={this.chosenResource.bind(this)} resource={resource} />
)
})
}
</div>
)
Then, in your SelectListItem's render() function, use like this:
return (
<div onClick={this.props.customClickEvent}>
...
</div>
)

You can provide onClick and other events only on DOM elements.

In react, we have components(ex. SelectListItem) and elements(ex.div). Elements are the basic building blocks for the component.
Elements are transpiled into plain javascript objects which represent DOM elements where we can bind the dom events like onCLick, onHover etc.
However Component returns the instance that contains a bunch of elements but does not correspond to the DOM element.
SO if you want to bind DOM event we should do it on the react elements, not in components.
In the above code, onClick event is passed down as props to the component which can be seen by logging the props of the compoenet.

Related

Spreading props to a list of Components

I'm trying to send a bunch of props to a Component.
In console.logs I noted that everything is working as I expected, every object has its correct value, every spread operation is working. But my Cards doesn't show in the page.
Is this way correct?
return (
<div>
{this.state.articles.forEach((card) => {
<ArticleCard {...card} />
})}
</div>
)
Image showing the problem
Array.forEach does not return anything. You need to use Array.map. Also you should be returning the component to be rendered in the callback.
return (
<div>
{this.state.articles.map((card) => (
<ArticleCard {...card} />
)}
</div>
)
Instead of using forEach, you should use map function on array, and with return keyword to return each and every ArticleCard
return(
<div>
{this.state.articles.map((card) => {
return <ArticleCard {...card} />
})}
</div>
)

React - Setting state to target with onClick method

I am trying to recreate a tabs component in React that someone gave me and I am getting stuck while getting the onClick method to identify the target.
These are the snippets of my code that I believe are relevant to the problem.
If I hardcode setState within the method, it sets it appropriately, so the onClick method is running, I am just unsure of how to set the tab I am clicking to be the thing I set the state to.
On my App page:
changeSelected = (event) => {
// event.preventDefault();
this.setState({
selected: event.target.value
})
console.log(event.target.value)
};
<Tabs tabs={this.state.tabs} selectedTab={this.state.selected}
selectTabHandler={() => this.changeSelected}/>
On my Tabs page:
{props.tabs.map(tab => {
return <Tab selectTabHandler={() => props.selectTabHandler()} selectedTab={props.selectedTab} tab={tab} />
})}
On my Tab page:
return (
<div
className={'tab active-tab'}
onClick={props.selectTabHandler(props.tab)}
>
{props.tab}
</div>
When I console.log(props.tab) or console.log(event.target.value) I am receiving "undefined"
There are a few issues causing this to happen. The first issue is that you wouldn't use event.target.value in the Content component because you aren't reacting to DOM click event directly from an onClick handler as you are in Tab, instead you are handling an event from child component. Also keep in mind that event.target.value would only be applicable to input or similar HTML elements that have a value property. An element such as <div> or a <span> would not have a value property.
The next issues are that you aren't passing the tab value from Tabs to Content and then from within Content to it's changeSelected() handler for selectTabHandler events.
In addition the onClick syntax in Tab, onClick={props.selectTabHandler(props.tab)} is not valid, you will not be able to execute the handler coming from props and pass the props.tab value. You could instead try something like onClick={() => props.selectTabHandler(props.tab)}.
Content - need to pass tab value coming from child to changeSelected():
render() {
return (
<div className="content-container">
<Tabs
tabs={this.state.tabs}
selectedTab={this.state.selected}
selectTabHandler={tab => this.changeSelected(tab)}
/>
<Cards cards={this.filterCards()} />
</div>
);
}
Tabs - need to pass tab coming from child to selectTabHandler():
const Tabs = props => {
return (
<div className="tabs">
<div className="topics">
<span className="title">TRENDING TOPICS:</span>
{props.tabs.map(tab => {
return (
<Tab
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
);
})}
</div>
</div>
);
};
export default Tabs;
Also don't forget the unique key property when rendering an array/list of items:
<Tab
key={tab}
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
Here is a forked CodeSandbox demonstrating the functionality.

React conditional rendering bug even with single parent and child list [duplicate]

This question already has answers here:
How can I return multiple lines JSX in another return statement in React?
(8 answers)
Closed 4 years ago.
Learning a bit of React but it seems to me like there's a conditional rendering bug with React itself.
Suppose I have a Foo component like so:
foo.js
import React, { Component } from 'react';
class Foo extends Component {
render() {
const isLoggedIn = this.props.isLoggedIn;
return(
<div>
{ isLoggedIn ? (
<div>one</div><div>two</div>
) : (
<div>one</div><div>two</div><div>three</div>
)}
</div>
);
}
}
export default Foo;
and I use it like so:
app.js
import React, { Component } from 'react';
import Foo from './components/foo';
class App extends Component {
render() {
return (
<div>
<Foo isLoggedIn={false} />
</div>
);
}
}
export default App;
This produces the error:
Syntax error: Adjacent JSX elements must be wrapped in an enclosing tag
Please note the above Foo component, there is only a single parent div being returned not array. If it was an array, then yes I agree with the error.
The official example given in the React document's example is like this:
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
https://reactjs.org/docs/conditional-rendering.html
Does this look like a bug in React to anyone?
Update
Based on the answers and comments given here, the implied behaviour of React is ternary operators inside the render() function comes with it's own render calls behind the scenes, acting like a virtual component, which would mean an extra layer of <div> needs to be wrapped around the list of my child elements.
Emberjs Foo component
My confusion arise from the fact I have done some Emberjs development in the past and a component like this works as expected:
<h3>Foo component</h3>
{{#if isLoggedIn}}
<div>one</div><div>two</div>
{{else}}
<div>one</div><div>two</div><div>three</div>
{{/if}}
Thanks for the explanation from everyone nonetheless.
You are returning the 2 or 3 divs in the condition. Instead you should wrap them into on div and return.
Notice the wrapper div below.
{ isLoggedIn ? (
<div className='wrapper'><div>one</div><div>two</div><div>
) : (
</div className='wrapper'><div>one</div><div>two</div><div>three</div></div>
)}
Also note that there is small typo below
<div>one</div><div>two</div><div>three</div
You have a syntax error
<div>one</div><div>two</div><div>three</div
should be
<div>one</div><div>two</div><div>three</div>
Did you try adding ?
return(
<div>
{ isLoggedIn ? (
<Fragment><div>one</div><div>two</div><Fragment>
) : (
<Fragment><div>one</div><div>two</div><div>three</div><Fragment>
)}
</div>
);
Syntax error: Adjacent JSX elements must be wrapped in an enclosing tag?
you are returning multiple sibling JSX elements in an incorrect manner.
In Foo:
return(
<div>
{ isLoggedIn ? (
<div>one</div> //are siblings without
<div>two</div> //wrapping in container element.
) : (
<div>one</div> //are siblings without
<div>two</div> //wrapping in
<div>three</div>//container element.
)}
</div>
);
Right approach :
return (
<div>
{isLoggedIn
? (
<div> //add wrapper
/...
</div>
)
: (
<div> //add wrapper
//...
</div>
)}
</div>
);
Or
If you are using React16 then you can use React.Fragement as well:
e.g.
<React.Fragment>
<div>one</div>
<div>two</div>
</React.Fragment>
You need to wrap the element rendered in the condition
return(
<div>
{ isLoggedIn ? (
<div><div>one</div><div>two</div></div>
) : (
<div><div>one</div><div>two</div><div>three</div></div>
)}
</div>
);
Note the extra div around the nested conditional elements

Gathering a collection of elements for a basic tab switcher

Objective
I want to make a basic tab switcher with React, starting with gathering a collection of tab elements.
Error
The array of HTML elements I'm able to assemble after my code has run contains a number of null entries equal to that of the actual elements.
Attempt
I have a wrapper for the entire structure, and here is its render method:
render(){
return (
<section className={"TR-Resume " + this.state.closedClass} ref="resume">
<div className={"TR-ResumeInner " + this.state.collapsedClass} ref="resumeInner">
<header className="TR-ResumeHeader">
<h2>Resumé</h2>
<Link to="/" className="TR-ResumeClose" onClick={this.close.bind(this)}>×</Link>
</header>
<section className="TR-ResumeMain js-ResumeTabs" ref="resumeTabs">
<header className="TR-ResumeTabs">
<Tabs className="js-Tabs" resume={this.props.resume.resume} tabRef={el => this.tabs.push(el)} />
</header>
<section className="TR-ResumeContent">
<Sections tabs={this.props.resume.resume} sectionRef={el => this.sections.push(el)} />
</section>
</section>
</div>
</section>
)
}
You'll notice I am using the React recommended method of exposing refs of children components to parents. I am appending each to a property of the parent component: tabRef={el => this.tabs.push(el)}
I understand I am re-declaring that collection every time the block renders, so to prevent appending the same elements to the collection, I'm clearing it in componentWillUpdate() lifecycle event:
componentWillUpdate() {
this.tabs.length = 0;
}
Result
My plan is to attach click events to the tabs to switch between panels, but I can't get that far, because by the time the componentDidUpdate() event fires, this is was is output in the array when I log it to the console:
[ null, null, null, null, a.TR-ResumeTab.js-Tab.TR-ResumeTab_active, a.TR-ResumeTab.js-Tab, a.TR-ResumeTab.js-Tab, a.TR-ResumeTab.js-Tab ]
Is there a better way to do this? Am I doing something wrong?
I plan to take these to animate tab panel with GSAP and want to avoid using jQuery.
Any help would be appreciated.
This is an issue with how you are using refs. When you do an inline ref declaration ( ref={ref => this.input = ref} ), the first time the component renders, it will receive a null value. Then on the second render, the refs will be correctly assigned. So that is why you first see the 4 null values, and then the 4 correct values.
To get around this, you should supply a class method to the ref prop instead of an inline function.
Example:
This:
render() {
return (
...
<Tabs className="js-Tabs" resume={this.props.resume.resume} tabRef={el => this.tabs.push(el)} />
...
)
}
Should be:
applyRef = ref => {
this.tabs.push(ref);
}
render() {
return (
...
<Tabs className="js-Tabs" resume={this.props.resume.resume} tabRef={this.applyRef} />
...
)
}
When you use a class method to apply a ref, it only gets called once when the actual element has been added to the dom, so you shouldn't get the initial null values anymore.

Calling a function for ALL child components

I have 3 components. They parent layout, a select box, and a panel this is generated x times from some data.
<Layout>
<Dropdown>
<Panel>
<Panel>
<Panel>
I'm trying to make it so when the select value changes, the contents of each panel changes. The changes are made by doing some math between the new select value, and data that is stored in the panel component. Each panel has different data.
Layout.js
updateTrueCost(selected){
this.refs.panel.setTrueCost
}
render(){
return (
<div>
<div class="row">
Show me the true cost in
<CurrencyDrop currencyChange = {(e) => this.updateTrueCost(e)} data = {this.state.data} />
</div>
<div class="row">
{this.state.data.map((item, index) => (
<Panel ref="panel" key = {index} paneldata= {item} />
))}
</div>
</div>
);
}
Panel.js
setTrueCost(selected){
//Do some math
this.setState({truecost: mathresult})
}
render(){
return(
<div>
{this.state.truecost}
</div>
)
}
CurrencyDrop.js
onHandelChange(e){
this.props.currencyChange(e);
}
render(){
return(
<Select
onChange={this.onHandelChange.bind(this)}
options={options} />
)
}
The current result is only the last panel updates when the select changes. I'm guessing I'm doing something wrong with the ref handling, but I must not be searching the right terms because I can't find any related questions.
Instead of calling ref's method use React build-in lifecycle methods.
class Panel extends React.Component {
componentWillReceiveProps (newProps) {
// compare old and new data
// make some magic if data updates
if (this.props.panelData !== newProps.panelData) {
this.setState({trueCost: someMath()});
}
}
render () {
return <div>{this.state.trueCost}</div>
}
}
Then just change input props and all data will be updated automatically.

Resources