How to add multiple onClick function on a button in ReactJS - reactjs

I have a tabs.js file that needs to be connected with some data. There are 4 tabs under tabs.js and on clicking each button some data should appear. I already have some onClick functionality on the 4 tabs. I cant get hold of how can i further add another functionality to each tab so that onclick of each tab, data should appear.
Please suggest a simple solution.
Here is my piece of code:
import React, { Component } from 'react'
class Tabs extends Component {
constructor(props) {
super(props);
this.state = { selectedTabIndex: -1};
this.handleChange = this.handleChange.bind(this);
}
handleChange(index) {
this.setState({ selectedTabIndex: index});
}
render() {
const { selectedTabIndex } = this.state;
const { tabs } = this.props;
return (
<ul className="tabs padding-left-large padding-top-small">
<li className={`tab-title ${selectedTabIndex==0?'active':''}`}><button onClick={()=>{this.handleChange(0)}}>{tabs[0]}</button></li>
<li className={`tab-title ${selectedTabIndex==1?'active':''}`}><button onClick={()=>{this.handleChange(1)}}>{tabs[1]}</button></li>
<li className={`tab-title ${selectedTabIndex==2?'active':''}`}><button onClick={()=>{this.handleChange(2)}}>{tabs[2]}</button></li>
<li className={`tab-title ${selectedTabIndex==3 || selectedTabIndex==-1?'active':''}`}><button onClick={()=>{this.handleChange(3)}}>{tabs[3]}</button></li>
</ul>
);
}
}
export default Tabs

As long as you're passing parameters to the function to me it looks like the easiest way to get each individual button to do something else would just be something like this:
handleChange(index) {
if(index === 1){
//do something for tab 1
}
if(index === 2){
//do something for tab 2
}
//etc.
this.setState({ selectedTabIndex: index});
}

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 store jsx into a variable using react?

I am new to programming. I want to implement the below,
when I click a list item I want to navigate to another page.
What I am trying to do? I have list items in a side panel. when I click on the list item it should navigate to another page.
So I have a listpanel component which renders the listitem component. On click list item, based on item_data_type it should take to the link got from get_type1_link method. however, it returns an object. I am not sure where I am making mistake.
class ListPanel extends react.purecomponent {
get_type1_link = () => {
const item_data = this.props.item_data;
const itemss = this.props.items;
const {itemname, item_id} = item_data.properties;
const filtered_item = items && items.find(item => item.id ===
item_id);
const item_name = (filtered_item) ? filtered_item.itemname :
(itemname ? itemname : item_id);
if (filtered_item) {
return (<Link to={`/someurl/${item_data.properties.item_id}`}>
{item_name}</Link>);
} else {
return <span>{item_name}</span>;
}
};
get_link = () => {
const item_data = this.props.item_data;
let link;
switch (item_data.type) {
case 'type1':
link = this.get_type1_link();
break;
case 'type2':
link = this.get_type2_link(); //some method similar to
//get_type1_link method
break;
default:
return link=window.location.href;
}
return link;
};
render = () => {
const list_item = this.props.;
return (
<ListItem
key={list_item.id}
text={this.get_text}
link={this.get_link}/>
);
}
class ListItem extends react.purecomponent {
render = () => {
<li onClick={props.link}>
<div className="text">
{this.props.text}
</div>
</li>
}
}
I think there is a problem in the way I am storing the value returned from get_type1_link method into variable link. since get_type1_link returns a jsx (Link). Could someone help me with this thanks.
I think issue is with your extends,
class ListPanel extends react.purecomponent {
It should be
class ListPanel extends React.PureComponent {
And also another issue is your render function, you have not return anything. Your render function should return like,
class ListItem extends React.PureComponent {
render = () => {
return(
<li onClick={this.props.link}> //props.link should be this.props.link
<div className="text">
{this.props.text}
</div>
</li>
)
}
}

Why is setState being reset?

I'm working on a community site where users can share and comment on websites (here it is)[https://beta.getkelvin.com/]. Websites can be filtered by category, but when a user goes into a specific page then backs out, the filter is removed.
Here's the process in steps:
User selects filter from drop down list, and this.state.topicSelected reflects the new value
User clicks a link to see a show page of an instance (a summary of a website), this.state.topicSelected reflects the correct value
User goes back to main page, and this.state.topicSelected is reverted back to 0
Instead of reverting back to 0, I want the value to still apply so the filter remains on the same category that the user selected before.
The problem seems to be that getInitialState is resetting the value of this.state.topicSelected back to 0 (as it's written in the code). When I try to put a dynamic value in 0's place, I get an undefined error.
Here's the getInitialState code:
var SitesArea = React.createClass({
getInitialState: function () {
return {
sortSelected: "most-recent",
topicSelected: 0 // need to overwrite with value of this.state.topicSelected when user selects filter
// I removed other attributes to clean it up for your sake
}
}
On here's the event:
onTopicClick: function (e) {
e.preventDefault();
this.setState({topicSelected: Number(e.target.value)});
if (Number(e.target.value) == 0) {
if (this.state.sortSelected == 'highest-score') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.score});
});
} else if (this.state.sortSelected == 'most-remarked') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.remarkable});
});
} else if (this.state.sortSelected == 'most-visited') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.visited});
});
} else if (this.state.sortSelected == 'most-recent') {
this.setState({sites: []}, function () {
this.setState({sites: this.state.recent});
});
}
} else {
this.getSites(this.state.sortSelected, Number(e.target.value));
this.setState({sites: []}, function () {
this.setState({sites: this.state.filter_sites});
});
}
And lastly, the dropdown menu:
<select
value={this.state.topicSelected}
onChange={this.onTopicClick}
className="sort"
data-element>
{
// Add this option in the .then() when populating siteCategories()
[<option key='0'value='0'>Topics</option>].concat(
this.state.topics.map(function (topic) {
return (<option
key={topic.id}
value={topic.id}>{topic.name}</option>);
}))
}
How do I get it so that this.state.topicSelected doesn't get reset when a user goes back to the main page?
I think your main page is getting unmounted (destroyed) when the user navigates from the main page to the summary page. React creates a brand new instance of the main page component when they return. That reconstruction initializes the selected topic back to 0.
Here is a codepen that shows what might be happening. Foo == your main page, Bar == summary page. Click Increment topic a couple times, then navigate from Foo to Bar and back again. Console logs show that the Foo component gets unmounted when you navigate away, and reconstructed on return.
Note You seem to be using an older version of react, as evidenced by the presence of getInitialState and React.createClass. My pen follows the more modern approach of initializing state in the class constructor.
To solve the problem, you have to save that state outside the main component in something that isn't getting deleted and re-created as they navigate. Here are some choices for doing that
Expose an onTopicSelected event from your main page. The parent of the main page would pass in a function handler as a prop to hook that event. The handler should save the selected topic in the component state of the parent. This is kind of messy solution because the parent usually should not know or care about the internal state of its children
Stuff the selected topic into something that isn't react, like window.props. This is an ugly idea as well.
Learn about redux and plug it into your app.
Redux is the cleanest way to store this state, but it would require a bit of learning. I have implemented the first solution in this codepen if you want to see what it would look like.
The original codepen showing the problem is pasted below as a snippet. Switch to Full page mode if you try to run it here.
//jshint esnext:true
const Header = (props) => {
const {selectedPage, onNavigationChange} = props;
const disableFoo = selectedPage == 'foo'
const disableBar = selectedPage == 'bar';
return (
<div>
<h1>Header Component : {selectedPage}</h1>
<button disabled={disableFoo} onClick={() => onNavigationChange('foo')}>Foo page</button>
<button disabled={disableBar} onClick={() => onNavigationChange('bar')}>Bar page</button>
<hr/>
</div>
);
};
class Foo extends React.Component {
constructor(props) {
console.log('Foo constructor');
super(props);
this.state = {
topicSelected: 0
};
this.incrementTopic = this.incrementTopic.bind(this);
}
incrementTopic() {
const {topicSelected} = this.state
const newTopic = topicSelected + 1
console.log(`incrementing topic: old=${topicSelected} new=${newTopic}`)
this.setState({
topicSelected: newTopic
})
}
render() {
console.log('Foo::render');
return (<div>
<h2>The Foo content page : topicSelected={this.state.topicSelected}</h2>
<button onClick={this.incrementTopic}>Increment topic</button>
</div>
);
}
componentWillMount() {
console.log('Foo::componentWillMount');
}
componentDidMount() {
console.log('Foo::componentDidMount');
}
componentWillUnmount() {
console.log('Foo::componentWillUnmount');
}
componentWillUpdate() {
console.log('Foo::componentWillUpdate');
}
componentDidUpdate() {
console.log('Foo::componentDidUpdate');
}
}
const Bar = (props) => {
console.log('Bar::render');
return <h2>The Bar content page</h2>
}
const Body = (props) => {
console.log('Body::render');
const {selectedPage} = props;
if (selectedPage == 'foo') {
return <Foo/>;
} else if (selectedPage == 'bar') {
return <Bar/>
}
};
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedPage: 'foo'
};
}
render() {
console.log('Application::render');
const {selectedPage} = this.state;
const navigationChange = (nextPage) => {
this.setState({
selectedPage: nextPage
})
}
return (
<div>
<Header selectedPage={selectedPage} onNavigationChange={navigationChange}/>
<Body selectedPage={selectedPage}/>
</div>
);
}
}
ReactDOM.render(<Application/>,
document.getElementById('main')
);
<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>
<div id="main"></div>

React: Add active class to selected Nav link on click

I'm trying to add an 'active' class to a nav link when it is clicked.
in JQuery I could do something like:
$('.navlink').on('click', function() {
$("a").removeClass('active');
$(this).toggleClass('active');
});
This is the closet I've managed to get in React:
export class NavLink extends Component {
constructor(props) {
super(props);
this.toggleClass=this.toggleClass.bind(this);
this.state = {
isActive: 1,
}
}
toggleClass = () => {
this.setState({ isActive: this.props.id });
}
render() {
const { href, name, id } = this.props;
const classNames = this.state.isActive === id ? "nav-link active" : "nav-link";
return (
<a
className={classNames}
onClick={this.toggleClass.bind(this)} href={href}>{name}</a>
)
}
}
The problem is React only re-renders the clicked on link. So once a link has been given a class of active it won't be removed if a different link is clicked.
You would need to do something like this in terms of components:
NavLinkList
|--NavLink
|--NavLink
NavLinkList would be the one holding the activeLinkId in its state.
That activeLinkId would then be passed to each Link as prop so when it is changed the Links would re-render.
Also in that NavLinkList you would have the function which would change the activeLinkId on the Links onClick handler.
I've done something like this before. It is easier to do this through the parent component. I didn't have components for each of my links. I just set their classes before the return in the render. Here is a sample of the code I did that I hope can be helpful to you.
I did the same thing with the onClick and the state just held a selected which held a String representing the name of the nav item. I had a Stories, Comments, and Likes nav links. Their original class was just 'tab'.
render () {
let storiesClass = 'tab';
let commentsClass = 'tab';
let likesClass = 'tab';
if (this.state.selected === "Stories") {
storiesClass += 'Selected';
commentsClass = 'tab';
likesClass = 'tab';
} else if (this.state.selected === "Comments") {
storiesClass = 'tab';
commentsClass += 'Selected';
likesClass = 'tab';
} else {
storiesClass = 'tab';
commentsClass = 'tab';
likesClass += 'Selected';
}
return (
<button className={storiesClass} onClick={this.selectTab("Stories")}>...

React onClick doesn't stop browser from setting anchor tag

I'm a React newbie and ran into a problem with paging controls using link tags. My basic paging control renders as something like this:
Next
The JSX definition that renders it looks like this:
<a href={"#page"+(this.props.pageIndex+1)} onClick={this.handleClick}>
{this.props.name}
</a>
The problem is that when you click on the Next link to go to Page 2, the browser ends up showing #page3 in the URL bar, even though the code properly renders page 2. (The code does nothing to modify the URL.) Tracing following the JavaScript in the debugger, I see that window.location.href stays at #page1, then jumps to #page3.
I believe what is happening is that React is intercepting the click event, and re-renders the page properly, then the browser's default link handling fires after the Next link has changed to point to #page3 instead of #page2.
Is my analysis correct? If so, what is the proper way to make this work so the browser shows #page2 in the URL bar?
EDIT: Here is the simplified code in context:
class RecordList extends React.Component {
changePage(pageIndex) {
console.log("change page selected: "+pageIndex);
this.props.changePage(pageIndex);
return false;
}
render() {
...
nextLink = (<PagingLink name=" Next> "pageIndex={this.props.pageIndex+1} handleClick={() =>
this.changePage(this.props.pageIndex+1)}/>)
return (
...
<div className="paging-control">
<span>Page {this.props.pageIndex+1}</span>
{nextLink}
</div>
);
}
}
class PagingLink extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.handleClick(this.props.pageIndex)
}
render() {
return (
<span className="pageLink">
<a href={"#page"+(this.props.pageIndex+1)} onClick={this.handleClick}>
{this.props.name}
</a>
</span>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
pageSize: 20,
pageIndex: 0,
...
};
}
componentDidMount() {
var pageIndex = this.state.pageIndex;
if (window.location.hash.indexOf("#page") === 0) {
pageIndex = Number(window.location.hash.substring(5))-1;
this.setState((prevState) => {
return { pageIndex: pageIndex };
}, () => {
this.fetchRecords(pageIndex);
});
}
else {
this.fetchRecords(pageIndex);
}
}
fetchRecords(pageIndex) {
...
}
changePage(pageIndex) {
console.log("change page selected: "+pageIndex);
this.setState({pageIndex: pageIndex});
this.fetchRecords(pageIndex);
}
render() {
var content = (
<RecordList
records={this.state.records}
pageSize={this.state.pageSize}
pageIndex={this.state.pageIndex}
changePage={(pageIndex) => this.changePage(pageIndex)}
/>
)
return (
<div className="App">
...
{content}
</div>
);
}
prevent the default event on the anchor :
handleClick(event) {
event.preventDefault()
this.props.handleClick(this.props.pageIndex)
}
I could not get this working reliably with my own event handling and url rewriting, so I decided to simply rebuild it using React Router V4. When the going gets tough, it's always a good idea to not reinvent the wheel and let somebody else do the hard work for you.

Resources