React Collapse - how do I toggle items in the list? - javascript

I am displaying a list of items from database and for each item, I have a button "Show more/less". When this is clicked, I want to show/hide the extra content with a nice slide down/up effect. I have implemented this functionality without the slide down/up effect, but want to use React Collapse to make it more user-friendly.
Here's the component where I am trying to implement the React Collapse functionality:
import React from 'react';
import ReactDOM from 'react-dom';
//import axios from 'axios';
import NewPost from './NewPost';
import {Collapse} from 'react-collapse';
class Posts extends React.Component {
constructor(props) {
super(props);
this.toggleClass= this.toggleClass.bind(this);
this.state = {
activeIndex: null
}
}
toggleClass(index, e) {
this.setState({ activeIndex: this.state.activeIndex === index ? null : index });
};
moreLess(index) {
if (this.state.activeIndex === index) {
return (
<span>
<i className='fas fa-angle-up'></i> Less
</span>
);
} else {
return (
<span>
<i className='fas fa-angle-down'></i> More
</span>
);
}
}
render () {
let content;
if (this.props.loading) {
content = 'Loading...';
} else {
content = this.props.posts.map((post, key) => {
return(
<li key={key}>
<div>
<span>{post.id}</span>
<span>{post.message}</span>
<button className="btn btn-primary btn-xs" onClick={this.toggleClass.bind(this, key)}>
{this.moreLess(key)}
</button>
</div>
<Collapse isOpened={true}>
<div className={'alert alert-info msg '+(this.state.activeIndex === key ? "show" : "hide")}>
{post.message}
</div>
</Collapse>
</li>
)
});
}
return (
<div>
<h1>Posts!</h1>
<div className="row">
<div className="col-md-6">
<ul>
{content}
</ul>
</div>
</div>
</div>
);
}
}
export default Posts
But when I click on the More/less button, the content in Collapse doesn't appear - after clicking the button nothing happens.
What am I missing here yet?

if you're using function and hooks I recommend this
import { Collapse } from "react-collapse";
import classNames from "classnames";
import React, { useState} from 'react';
export default function yourFunction() {
const [activeIndex, setActiveIndex] = useState(null);
return(
{groups.map((group, index) => (
<button className="btn btn-primary navbar-toggler"
type="button"
data-toggle="collapse"
onClick={event => setActiveIndex(
activeIndex === index ? null : index
)}
data-target="#collapseExample"
aria-expanded="false"
aria-controls="collapseExample">
[CLICK HERE]
</button>
<Collapse isOpened={activeIndex === index}>
<div
className={classNames("alert alert-info msg", {
show: activeIndex === index,
hide: activeIndex !== index
})}
>
<a>[YOUR COLLAPSE CONTENT]</a>
</div>
</Collapse>
)
}

You didn't bind correct check to <Collapse isOpened={true}>. Instead of true, you should put (this.state.)activeIndex === index (current item index) like this:
<Collapse isOpened={this.state.activeIndex === index}>
So it can actually collapse due to activeIndex. I've made codesandbox for you so you can make sure it works: https://codesandbox.io/s/jzx44ynyqw
But I think this is the most important part of it (note that your index was called key, I just renamed it for convenience):
<li key={index}>
<div>
<p>{post.title}</p>
<Collapse isOpened={activeIndex === index}>
<div
className={classNames("alert alert-info msg", {
show: activeIndex === index,
hide: activeIndex !== index
})}
>
{post.message}
</div>
</Collapse>
<button
className="btn btn-primary btn-xs"
onClick={this.toggleClass.bind(this, index)}
>
{this.moreLess(index)}
</button>
</div>
</li>

Related

How to create "Selected tab" in Next.Js?

I am trying to create selected tab in Next.Js.
The User will have the option to search for data it can be Users or Posts, what the user will search for will be selected by clicking on one of the buttons.
Once the user clicks on the button the button will change background to blue.
However I can't make it to work properly, when the User clicks on the button the .Selected class gets added to the button but the button doesn't render the CSS.
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState < String > ();
const setSearchOption = (searchFor: String) => {
let searchOption = '';
if (searchFor == 'POSTS') {
searchOption = 'POSTS';
} else {
searchOption = 'USERS';
let button = document.getElementById('usersOption') as HTMLElement;
button.className += style.Selected;
}
console.log(searchOption);
setPostsSearch(searchOption);
}
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...' />
</div>
<div className={style.SearchOptions}>
<button id='usersOption' onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement) {
return (
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
you can use searchOption data for className style
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState<String>();
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={searchPosts === 'USERS' ? style.Selected : undefined } onClick={() => setPostsSearch('USERS')}>Users</button>
<button id='postsOption' className={searchPosts === 'POSTS' ? style.Selected : undefined } onClick={() => setPostsSearch('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement){
return(
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
Just have a state for active searchOption and apply the class conditionally directly into the JSX.
const [activeSearchOption, setActiveSearchOption] = useState('USERS')
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={activeSearchOption === 'USERS' ? 'active' : ''} onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' className={activeSearchOption === 'POSTS' ? 'active' : ''} onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)

How to optimize toggling className between different menu items by React?

I have header component, where I want to toggle className between all the elements of menu (if one of the elements of menu is active and user is clicking to another element - this element become active and all others no). I have a code like this
import React, { useState } from 'react';
import './header.scss';
export const Header = ({ favoriteCount }) => {
const [activeIndex, setActiveIndex] = useState(0);
function toggleClass(index) {
setActiveIndex(index);
}
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
<li>
<a
className={
activeIndex === 0
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Characters
</a>
</li>
<li>
<a
className={
activeIndex === 1
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Favorites
</a>
</li>
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};
and styles to visualise toggling classes
&-menu__link {
color: lightgray;
}
.active {
color: #fff;
}
This approach is working but looks creepy. Maybe somebody knows how to optimize it?
I wouldn't use the index, I'd use the text of the item. I'd also include that text in the href so that there's an indication of what the anchor leads to. To avoid repeated code, you might put the menu items in a reusable array, something like this:
const menuItems = [
"Characters",
"Favorites",
];
export const Header = ({ favoriteCount }) => {
const [activeItem, setActiveItem] = useState("");
const setActiveItem = useCallback((event) => {
setActiveItem(event.currentTarget.href.substring(2));
}, []);
const list = menuItems.map(item =>
<li key={item}>
<a
className={`header-menu__link ${item === activeItem ? "active" : ""}`}
onClick={setActiveItem}
href={"##" + item}>
{item}
</a>
</li>
);
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
{list}}
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};

React JS- Multiple Buttons

I'm trying to make multiple toggles buttons, and whenever the user clicks on any one of them, then I want it to become a bit darker. So, that user knows which one they've clicked.
Here's sample code I've written.
CODE:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [active, setActive] = useState("first");
return (
<>
<div className="App">
<button className="button" active={active === "first"} onClick={() => {setActive("first"); }} >first</button>
<button className="button" active={active === "Second"} onClick={() => {setActive("Second"); }} >Second</button>
<button className="button" active={active === "Third"} onClick={() => {setActive("Third"); }} >Third</button>
</div>
</>
);
}
Now, How do I make an OnClick function that holds the after Click? functionality?
You can do it like this:
<button
className={`button ${active === "first" ? "activeButton" : ""}`}
onClick={() => {
setActive("first");
}}
>
first
</button>
<button
className={`button ${active === "Second" ? "activeButton" : ""}`}
onClick={() => {
setActive("Second");
}}
>
Second
</button>
<button
className={`button ${active === "Third" ? "activeButton" : ""}`}
onClick={() => {
setActive("Third");
}}
>
Third
</button>
And then in you css file:
.activeButton {
background-color: red;
}

How do I pass click event to child component

I have a two react bootstrap buttons within a dropdown and I am trying to understand why the onClick event is not working. The button are returned from function. I can get the event to fire if I just use Button in the main render function. I'd like to know why this is happening. Is returning the component from a function ok? Or do I need to use a class;
Code:
{this.state.filterDropdowns.map((value, indexNo) =>
<Dropdown
className="button"
key={`${indexNo}`}
>
<Dropdown.Toggle variant="light" size="sm">
{value.name}
</Dropdown.Toggle>
<Dropdown.Menu className="custommenu">
{value.data.result.map((input, index) =>
<div key={`${index}`}>
<input
key={`${index}`}
id={input}
type="checkbox"
defaultChecked
onClick={(e) => this.clickFilterBox(e, indexNo)}
>
</input>{" " + input}<br></br>
</div>
)}
<Dropdown.Divider />
<CustomButtons
length={value.data.result.length}
index={indexNo}
onClear={(e) => this.onClear(e)}
onSelect={(e) => this.onSelect(e)}
>
</CustomButtons>
</Dropdown.Menu>
</Dropdown>
)}
Custom button function
const CustomButtons = function(props) {
if (props.length < 10) {
return(<div></div>);
} else {
return (
<div >
<div className="select-div">
<Button
size="sm"
onClick={props.onSelect}
>
Select
</Button>
</div>
<div className="clear-div">
<Button
size="sm"
onClick={props.onClear}
>
Clear
</Button>
</div>
</div>
);
}
}
You need to change your implementation as follows:
<CustomButtons
length={value.data.result.length}
index={indexNo}
onClear={this.onClear.bind(this)}
onSelect={this.onSelect.bind(this)}
/>
Then in the function implement it like this:
<Button
size="sm"
onClick={(e) => props.onSelect(e)}
>
Select
</Button>
For anyone interested this is a solution:
Created a class for custom buttons
import React from 'react';
import {Button} from 'react-bootstrap';
export default class CustomButtons extends React.Component {
constructor(props) {
super(props);
this.state = {
length: props.length,
onClear: props.onClear,
onSelect: props.onSelect
}
}
render() {
if (this.state.length < 10) {
return(<div></div>);
} else {
return (
<div >
<div className="select-div">
<Button
size="sm"
onClick={this.state.onSelect}
>
Select
</Button>
</div>
<div className="clear-div">
<Button
size="sm"
onClick={this.state.onClear}
>
Clear
</Button>
</div>
</div>
);
}
}
}
Then in main app component
<CustomButtons
length={value.data.result.length}
index={indexNo}
onClear={(e) => this.onClear(e)}
onSelect={(e) => this.onSelect(e)}
>
</CustomButtons>

How to sort data and display it in reactjs?

I want to display projects in sorted manner when user clicks on sort by funds then it should display the projects in sorted by funds but my code is not working why so ? I am importing the sortBy() function and using it when user clicks on the button.
home.js:
import React, { Component } from 'react';
import Card from '../common/card';
import Projects from '../../data/projects';
import { sortBy } from './helper';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
projects: Projects
}
}
render() {
return (
<div>
<div className="header">
<div className="buttonContainer">
<div>
<button className="btn btn-primary mycustom dropdown-toggle mr-4" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">Sort by </button>
<div className="dropdown-menu">
<a className="dropdown-item" href="#" onClick={() => sortBy('funded')}>Percentage fund</a>
<a className="dropdown-item" href="#" onClick={() => sortBy('backers')}>Number of backers</a>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
{this.state.projects.map( (val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
}
helper.js:
import projects from '../../data/projects';
export function sortBy (searchTerm) {
if(searchTerm === 'funded'){
return projects.sort((a,b) => a.funded - b.funded);
}else if(searchTerm === 'backers'){
return projects.sort((a,b) => a.backers - b.backers);
}
}
projects.js:
http://jsfiddle.net/0z8xcf1o/
In your render function, you should iterate over this.state.projects instead of Projects
You need to update your state on every sort
Your sortBy function can be simplified, it should set the state for you, and it would be better to add it to the component:
-
sortBy(searchTerm) {
this.setState({ projects: [...Projects].sort((a, b) => a[searchTerm] - b[searchTerm]) });
}
and then call it with
onClick={() => this.sortBy('funded')}

Categories

Resources