how to implement tabs in react with using any library? - javascript

could you please tell me how to implement tabs in react with using any library ?
I follow this link and tried to make tabs
https://toddmotto.com/creating-a-tabs-component-with-react/
but not succeeded.
Here is my code:
https://codesandbox.io/s/D9Q6qWPEn
import React, {Component} from 'react';
class Tabs extends Component {
constructor() {
super();
this.state= {
selected:0
}
}
_renderContent() {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
}
render() {
return (
<div className="tabs">
{this._renderContent()}
</div>
)
}
}
export default Tabs;
I am not able to show tabs and it there individual contents after click
any update ?

If I understand your problem correctly, you just want to display labels for your tabs.
You can do this in many ways in React but without changing your code too much, you can accomplish this task by changing render() method of Tabs component, like below:
setTab(index) {
this.setState({
selected: index,
});
}
_renderLabels() {
return this.props.children.map((child, index) => (
<div key={child.props.label} onClick={() => { this.setTab(index) }}>
{child.props.label}
</div>
));
}
render() {
return (
<div className="tabs">
{this._renderLabels()}
{this._renderContent()}
</div>
);
}
What we do here is basically rendering labels based on props.children. For each label we append click handler.
You could improve this code by creating specific components like TabPane, TabLabel.
Full code of Tabs component.
import React, { Component } from "react";
class Tabs extends Component {
constructor() {
super();
this.state = {
selected: 0
};
}
setTab(index) {
this.setState({
selected: index,
});
}
_renderContent() {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
}
_renderLabels() {
return this.props.children.map((child, index) => (
<div key={child.props.label} onClick={() => { this.setTab(index) }}>
{child.props.label}
</div>
));
}
render() {
return (
<div className="tabs">
{this._renderLabels()}
{this._renderContent()}
</div>
);
}
}
export default Tabs;
Demo: https://codesandbox.io/s/pRvG6K0r

I implemented this and is working awesome:
I'm using a functional component with Hooks.
const Tabs = () => {
const [currentTab, setCurrentTab] = useState('first-tab');
const toggleTab = () => {
switch (currentTab) {
case 'first-tab':
return <FirstTab />;
case 'second-tab':
return <SecondTab />;
default:
return null;
}
};
return (
<div>
<div>
<h3 onClick={() => setCurrentTab('first-tab')}>First Tab</h3>
<h3 onClick={() => setCurrentTab('second-tab')}>Second Tab</h3>
</div>
{toggleTab()}
</div>
);
};
You can add more components if you want.
Psdt: And if you want to add styles to the Current Tab Title you can create another useState Hook, something like "isActive" and this set it up on the tag classes.

const Tabs = () => {
const tabsData = [
{ id: 0, tabName: "first tab", tabData: "first tab data" },
{ id: 1, tabName: "second tab", tabData: "second tab data" },
{ id: 2, tabName: "third tab", tabData: "third tab data" },
];
const [activeIndex, setActiveIndex] = useState(false);
const tabClick = (index) => {
setActiveIndex(index);
};
return (
<>
<ul>
{tabsData.map((tab) => {
return (
<li key={tab.id} onClick={() => tabClick(tab.id)}>
{tab.tabName}
</li>
);
})}
</ul>
<ul>
{tabsData.map((tab) => {
if (tab.id === activeIndex) {
return <li key={tab.id}>{tab.tabData}</li>;
}
})}
</ul>
</>
);
};

if all you have is a hammer, everything looks like a nail
Don't try to use React for everything. Not only is this a bad practice. React was never intended to fit that purpose. Creating tabs is 90% CSS and only 10% JS (controlling which one gets the "active" state.
I created an example that implements tabs 100% using CSS, It doesn't work well since only after a tab is clicked the content is shown, there is no default.
If you wanted to automate this (e.g.) in an React component The only thing you have to do is bind a tab click to an active state a classname representing the active state (e.g.: tab--active). And create a default.
.tabs__list-item {
display: inline;
}
.tab {
display: none;
}
.tab:target { /* Only works if tab is clicked, can be fixed with React. */
display: block;
}
<nav class="tabs">
<ul class="tabs__list">
<li class="tabs__list-item"><a class="tabs__link" href="#tab1">Tab 1</a></li>
<li class="tabs__list-item"><a class="tabs__link" href="#tab2">Tab 2</a></li>
<li class="tabs__list-item"><a class="tabs__link" href="#tab3">Tab 3</a></li>
</ul>
</nav>
<section class="tab" id="tab1">
<h1>Tab 1 header</h1>
<p>This is the content of tab 1.</p>
</section>
<section class="tab" id="tab2">
<h1>Tab 2 header</h1>
<p>This is the content of tab 2.</p>
</section>
<section class="tab" id="tab3">
<h1>Tab 3 header</h1>
<p>This is the content of tab 3.</p>
</section>
In short:
Write the HTML structure / CSS markup first
Observe missing logic
Implement missing logic using the best tool.
React may be a valid tool for this purpose but start by having React rendering your HTML and CSS. If you can't get the "wiring" logic to work open a question indicating what specific problem you encounter.

Related

#szhsin/react-menu can't get Menu items inline, always one on top of another

I'm trying to get the Menu (from #szhsin/react-menu module) element buttons to show up to the right of the previous generated item, however I'm a bit lost as to how to get it to do so. Everything results in the element showing below previous.
import React from 'react';
import {
Menu,
MenuItem,
MenuButton,
SubMenu
} from '#szhsin/react-menu';
import '#szhsin/react-menu/dist/index.css'
class TopMenuDropdown extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div>
{this.props.TMPMenuTestCategory.map (({name,items},i) =>
{
return <Menu
align={'end'}
key={i}
menuButton={<MenuButton>{name}</MenuButton>}
reposition={'initial'}
>
{items.map((item,j) =>
{
console.log(item,j);
return <MenuItem key={j}>{item}</MenuItem>
}
)}
</Menu>
} )}
</div>
)
}
}
I was looking through the documentation on https://szhsin.github.io/react-menu/docs , however, me trying the following has had no effect:
Assigning the display:'inline' or 'flex' the <Menu> or to a <div><Menu> as I attempted to give each menu it's own div when generated.
Wrapping each generated menu in a <span>
Fiddling with the Menu item's props like 'align' , 'position' , and 'reposition' (though I'm guessing Reposition needs an additional RepositionFlag to work if I understand it correctly)
Here's the snippet of index.JS it is part of
const basicMenuArray = [
{ name: 'ProTIS', items: [ 'Login', 'Exit' ] },
{ name: 'Project', items: [ 'Open', 'Info' ] },
]
class App extends React.Component {
state={
language:'sq'
}
render () {
return (
<div >
<div style={{display:'flex', width:'75%', float:'left' }}>
<span> Temp Text </span>
</div>
<div style={{display:'flex', width:'25%'}}>
<span style={{marginLeft:'auto'}}>
<DataComboBox
dropdownOptions={languages}
value={this.state.language}
valueField='language_code'
textField='language_full_name'
onChange={(value) => alert(JSON.stringify(value))}
/>
</span>
</div>
<div>
<TopMenuDropdown TMPMenuTestCategory={basicMenuArray} />
</div>
</div>
);
}
}
So I ended up realizing something this morning, as I'm learning ReactJS still, and my brain did not process the things properly.
I changed the initial
<div>
to
<div style={{display:'flex'}}>
and added a style={{display:'flex', float:'left'}} to the <Menu> which generates the button.
the final code snippet looks like this for anyone still learning like I am :)
return (
<div style={{display:'flex'}}>
{this.props.TMPMenuTestCategory.map (({name,items},i) =>
{
return <Menu
style={{display:'flex', float:'left'}}
key={i}
menuButton={<MenuButton>{name}</MenuButton>}
>
{items.map((item,j) =>
{
console.log(item,j);
return <MenuItem key={j}>{item}</MenuItem>
}
)}
</Menu>
} )}
</div>
)

I am trying to print array inside object using map function but I get :Cannot read property map of undefined

I'm trying to print the properties of Selectedproduct object inside Modal section and every thing works well until it reaches to "description" array property , it shows me "Cannot read property 'map' of undefined". eventhough when I use console.log(Selectedproduct) the description property appears normally,but when I code console.log(Selectedproduct.description) I dont know why it consider it as undefined .can you please tell me why it can't see the description as stand alone property ?
import React, { Component } from "react";
import FormatCurrency from "../Components/util";
import Slide from "react-reveal/Slide";
import Modal from "react-modal";
import Zoom from "react-reveal/Zoom";
import { connect } from "react-redux";
import { GetProducts } from "../Actions/ItemsActions";
import { AddToCart } from "../Actions/CartActions";
class Products extends Component {
constructor(props) {
super();
this.state = {
show: false,
Selectedproduct: {},
};
}
showModal = (product) => {
console.log(product);
this.setState({ show: true, Selectedproduct: product });
};
hideModal = () => {
this.setState({ show: false });
};
componentDidMount() {
this.props.GetProducts();
}
render() {
const { Selectedproduct } = this.state;
return (
<div>
<Slide left cascade={true}>
{!this.props.products ? (
<div> Loading..</div>
) : (
<ul className="products">
{this.props.products.map((product) => (
<li key={product._id}>
<div className="product">
<a href={"#" + product._id}>
<img
src={product.image}
alt={product.title}
onClick={() => this.showModal(product)}
></img>
<p>{product.title}</p>
</a>
<div className="product-price">
<div> {FormatCurrency(product.price)}</div>
<button
onClick={() => this.props.AddToCart(product)}
className="button primary overlay"
>
{" "}
Add to cart
</button>
</div>
</div>
</li>
))}
</ul>
)}
</Slide>
<Modal isOpen={this.state.show} onRequestClose={this.hideModal}>
<Zoom>
<button className="close-modal" onClick={this.hideModal}>
x
</button>
<div className="product-details">
<img
src={Selectedproduct.image}
alt={Selectedproduct.title}
></img>
<div className="product-details-description">
<p>{Selectedproduct.title}</p>
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
<div className="product-price">
<div>{FormatCurrency(Selectedproduct.price)}</div>
<button
className="button primary"
onClick={() => {
this.props.AddToCart(Selectedproduct);
this.hideModal();
}}
>
{" "}
Add to cart
</button>
</div>
</div>
</div>
</Zoom>
</Modal>
</div>
);
}
}
export default connect((state) => ({ products: state.products.filterdItems }), {
GetProducts,
AddToCart,
})(Products);
Try this as your state property seems still undefined at runtime.
{Selectedproduct.description.map((x)=>(<li>x</li>))}
replace with:
{Selectedproduct && Selectedproduct.description? Selectedproduct.description.map((x)=>(<li>x</li>)):null}
description is likely undefined. Instead of:
<ul>
{Selectedproduct.description.map((x)=>(<li>x</li>))}
</ul>
just put in this temporary code to try and see what your object really looks like:
<ul>
console.dir("### DESCRIPTION IS:", Selectedproduct.description)
</ul>
and the open your browser dev tools to see what this prints to the console.
UPDATE based on comment after using console.log:
If you are getting something like availableColors: Array(2) for Selectedproduct you cannot print an array out to your <li> tags. An array is not a string. You have to unnest the inner arrays first.
So if your structure is Selectedproduct.description.availableColors = ['blue', 'red'] just as an example, you will need code like:
const { availableColors, otherAttribute1, otherAttribute2 } = Selectedproduct.description // destructure all array attributes from description
...
and then later in the component, do:
<ul>
{ availableColors.map(_ => <li>_</li>)}
{ otherAttribute1.map(_ => <li>_</li>)}
{ otherAttribute2.map(_ => <li>_</li>)}
</ul>

Expand/Collapse all data

I am making a Accordion and when we click each individual item then its opening or closing well.
Now I have implemented expand all or collapse all option to that to make all the accordions expand/collapse.
Accordion.js
const accordionArray = [
{ heading: "Heading 1", text: "Text for Heading 1" },
{ heading: "Heading 2", text: "Text for Heading 2" },
{ heading: "Heading 3", text: "Text for Heading 3" }
];
.
.
.
{accordionArray.map((item, index) => (
<div key={index}>
<Accordion>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text expandAll={expandAll}>
<p className="text">{item.text}</p>
</Text>
</Accordion>
</div>
))}
And text.js is a file where I am making the action to open any particular content of the accordion and the code as follows,
import React from "react";
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
{this.props.expandAll ? (
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
) : (
<div className={`content ${this.props.text ? "open" : ""}`}>
{this.props.text ? this.props.children : ""}
{this.props.text
? this.props.render && this.props.render(this.props.text)
: ""}
</div>
)}
</div>
);
}
}
export default Text;
Here via this.props.expandAll I am getting the value whether the expandAll is true or false. If it is true then all accordion will get the class className={`content open`} so all will gets opened.
Problem:
The open class is applied but the inside text content is not rendered.
So this line doesn't work,
{this.props.render && this.props.render(this.props.text)}
Requirement:
If expand all/collapse all button is clicked then all the accordions should gets opened/closed respectively.
This should work irrespective of previously opened/closed accordion.. So if Expand all then it should open all the accordion or else needs to close all accordion even though it was opened/closed previously.
Links:
This is the link of the file https://codesandbox.io/s/react-accordion-forked-sm5fw?file=/src/GetAccordion.js where the props are actually gets passed down.
Edit:
If I use {this.props.children} then every accordion gets opened.. No issues.
But if I open any accordion manually on click over particular item then If i click expand all then its expanded(expected) but If I click back Collapse all option then not all the accordions are closed.. The ones which we opened previously are still in open state.. But expected behavior here is that everything should gets closed.
In your file text.js
at line number 9. please replace the previous code by:
{this.props.children}
Tried in the sandbox and worked for me.
///
cant add a comment so editing the answer itself.
Accordian.js contains your hook expandAll and the heading boolean is already happening GetAccordian.js.
I suggest moving the expand all to GetAccordian.js so that you can control both values.
in this case this.props.render is not a function and this.props.text is undefined, try replacing this line
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
by this:
<div className={`content open`}>
{this.props.children}
</div>
EDIT: //
Other solution is to pass the expandAll property to the Accordion component
<Accordion expandAll={expandAll}>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text>
<p className="text">{item.text}</p>
</Text>
</Accordion>
then in getAccordion.js
onShow = (i) => {
this.setState({
active: this.props.expandAll ? -1: i,
reserve: this.props.expandAll ? -1: i
});
if (this.state.reserve === i) {
this.setState({
active: -1,
reserve: -1
});
}
};
render() {
const children = React.Children.map(this.props.children, (child, i) => {
return React.cloneElement(child, {
heading: this.props.expandAll || this.state.active === i,
text: this.props.expandAll || this.state.active + stage === i,
onShow: () => this.onShow(i)
});
});
return <div className="accordion">{children}</div>;
}
};
Building off of #lissettdm answer, it's not clear to me why getAccordion and accordion are two separate entities. You might have a very valid reason for the separation, but the fact that the two components' states are interdependent hints that they might be better implemented as one component.
Accordion now controls the state of it's children directly, as before, but without using getAccordion. Toggling expandAll now resets the states of the individual items as well.
const NormalAccordion = () => {
const accordionArray = [ //... your data ];
const [state, setState] = useState({
expandAll: false,
...accordionArray.map(item => false),
});
const handleExpandAll = () => {
setState((prevState) => ({
expandAll: !prevState.expandAll,
...accordionArray.map(item => !prevState.expandAll),
}));
};
const handleTextExpand = (id) => {
setState((prevState) => ({
...prevState,
[id]: !prevState[id]
}));
};
return (
<>
<div className="w-full text-right">
<button onClick={handleExpandAll}>
{state.expandAll ? `Collapse All` : `Expand All`}
</button>
</div>
<br />
{accordionArray.map((item, index) => (
<div key={index}>
<div className="accordion">
<Heading handleTextExpand={handleTextExpand} id={index}>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text shouldExpand={state[index]}>
<p className="text">{item.text}</p>
</Text>
</div>
</div>
))}
</>
);
};
Heading passes back the index so the parent component knows which item to turn off.
class Heading extends React.Component {
handleExpand = () => {
this.props.handleTextExpand(this.props.id);
};
render() {
return (
<div
style={ //... your styles}
onClick={this.handleExpand}
>
{this.props.children}
</div>
);
}
}
Text only cares about one prop to determine if it should display the expand content.
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
<div
className={`content ${this.props.shouldExpand ? "open" : ""}`}
>
{this.props.shouldExpand ? this.props.children : ""}
</div>
</div>
);
}
}

Returning multiple HTML snippets per element of array

Edited --
Let's say I have an array of JSON objects:
"social":[
{
"name":"facebook",
"className":"fa fa-facebook"
},
{
"name":"linkedin",
"className":"fa fa-linkedin"
},
{
"name":"instagram",
"className":"fa fa-instagram"
},
{
"name":"github",
"className":"fa fa-github"
}
]
How do I create an snippet for each of the objects such that they return
<p>{social.name}<p>
And I don't want to use map.
This is generalized for a more complicated example, but this seems to be the problem I am facing (i.e. I have the data in the format below and I need to get the property from each of the elements to display and I only have one function)
Assuming that social is a part of the state, you can implement a method that maps each item in the social array to a p tag:
renderSocialNames = () => {
return this.state.social.map(
socialItem => <p key={socialItem.className}>{socialItem.name}</p>
);
}
Here's a Working Sample StackBlitz for your ref.
cleaner code :) , this might solve your issue
import React, { Component } from "react";
class Projects extends Component {
constructor(props) {
//initialize this component with props
super(props);
}
render() {
const { data } = this.props;
if (data) {
const projects = data.map(project => {
return (
<a className="cell" data-remodal-target={project.id}>
<img
className="grid-image"
src={project.cover}
data-aload={projects.cover}
alt={project.name}
/>
</a>
);
});
const modals = data.map(project => {
return (
<div className="remodal" data-remodal-id={project.id}>
<button
data-remodal-action="close"
className="remodal-close"
></button>
<h1>Remodal</h1>
<p>
Responsive, lightweight, fast, synchronized with CSS animations,
fully customizable modal window plugin with declarative
configuration and hash tracking.
</p>
<br />
<button data-remodal-action="cancel" className="remodal-cancel">
Cancel
</button>
<button data-remodal-action="confirm" className="remodal-confirm">
OK
</button>
</div>
);
});
}
return (
<section id="projects">
<div className="grid-container remodal-bg">
{projects}
{modals}
</div>
</section>
);
}
}

target only one item from list on each click reactjs

Hey I'm quite new to programming and I'm trying to solve this exercise and I got stuck.
Show 10 posts from the API in the browser - Working
For each post show 3 related comments - Working
The problem is that when I click one Post from the feed, the click function will fetch and display the all other comments below the respective posts at the same time...What I'm trying to accomplish is to onClick display comment to the related post and hide it when clicked on other post.
Also I need to show a button "load more" every time a set of comments appears and fetch the latest 10 comments when clicked.
Any Help, Suggestions on how to keep things clean and readable would be appreciated!
Thank you in advance;
:)
Code Below:
import React from "react";
import axios from "axios";
const postsID = "/posts";
const commentsID = "/comments";
var postsURL = `https://jsonplaceholder.typicode.com${postsID}`;
var commentsURL = `https://jsonplaceholder.typicode.com${commentsID}`;
class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [],
comments: [],
expanded: false,
commentsToShow: 3
};
this.clicked = this.clicked.bind(this);
}
/*
showMoreComments() {
}
*/
clicked() {
axios.get(commentsURL).then(res => {
console.log("comments:", res);
this.setState({ comments: res.data });
});
}
componentDidMount() {
axios.get(postsURL).then(res => {
console.log("posts:", res);
this.setState({ posts: res.data });
});
}
render() {
//console.log('VARIABLE WORKING!', postsURL);
return (
<div className="container">
<div className="jumbotron-div col s12">
<ul className="collection">
{this.state.posts.slice(0, 10).map(post => (
<div>
<div key={post.id} onClick={this.clicked}>
<h5>User ID: {post.id}</h5>
<p>Post: {post.body}</p>
</div>
<div>
<ul className="collection">
{this.state.comments
.filter(comment => comment.postId === post.id)
.slice(0, 3)
.map(comment => (
<li key={comment.id}>
<p>Comment ID: {comment.postId}</p>
<p>Comment: {comment.body}</p>
</li>
))}
</ul>
</div>
</div>
))}
</ul>
</div>
</div>
);
}
}
export default Posts;
If a Post can show its comments or hide it, then it definetly needs its own state. Therefore, it needs to be an own component, e.g.:
class Post extends React.Component {
constructor(props) {
super(props);
this.state = { showComents: false };
}
render() {
const { id, body, comments } = this.props;
return (
<div key={id} onClick={() => this.setState({showComments: true })}>
<h5>User ID: {id}</h5>
<p>Post: {body}</p>
</div>
<div>
<ul className="collection">
{this.state.showComments ? comments.slice(0, 3)
.map(comment => (
<li key={comment.id}>
<p>Comment ID: {comment.postId}</p>
<p>Comment: {comment.body}</p>
</li>
)) : ""}
</ul>
</div>
</div>
))}
);
}
}
Then use <Post /> inside of Posts and pass down all the data the Post needs.

Categories

Resources