How to change to Active when its clicked - javascript

I have a list and it's already set to Active, I mean the first list is already active, but my question is how to make the other list active also only when it's clicked and as long it's clicked and on the same page it keeps active.
return (
<div className="sidebar">
<div className="sidebarWrapper">
<div className="sidebarMenu">
<h3 className="sidebarTitle">Dashboard</h3>
<ul className="sidebarList">
<Link to="/" className="link">
<li className="sidebarListItem active">
<LineStyleIcon className="sidebarIcon" />
Home
</li>
</Link>
<li className="sidebarListItem">
<TimelineIcon className="sidebarIcon" />
Analytics
</li>
<li className="sidebarListItem">
<TrendingUpIcon className="sidebarIcon" />
Sales
</li>
</ul>
);
CSS:
.sidebarTitle {
font-size: 18px;
font-weight: 700;
color: rgb(187, 186, 186);
}
.sidebarList {
list-style: none;
padding: 5px;
}
.sidebarListItem.active,
.sidebarListItem:hover {
background-color: black;
color: white;
}

Your component will need to keep state for the currently active item. The initial state can be 0, the index of the first element in the list of items. When another item is clicked, we can setActive(index) for the index of the clicked item. Determining an individual item's active property is a derivative of the active state and the item's index, active == index -
function App({ items = [] }) {
const [active, setActive] = React.useState(0)
return items.map((value, index) =>
<ListItem
key={index}
value={value}
active={index == active}
onClick={_ => setActive(index)}
/>
)
}
function ListItem({ value, active, onClick }) {
return <button
type="button"
class={active ? "active" : "inactive"}
onClick={onClick}
children={value}
/>
}
ReactDOM.render(<App items={["🫐", "🍑", "🥝"]}/>, document.body)
button { border-color: dodgerblue; font-size: 4rem; background-color: white; }
.active { border-color: tomato; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

I would store the list items in an array so that I can loop over them later
const listItems = ["Home", "Analytics", "Sales"];
and I would create a state to store the active index
const [activeIndex, setActiveIndex] = React.useState(0);
and finally I would render the list items like this:
<ul className="sidebarList">
{listItems.map((listItem, index) => {
return (
<li
onClick={() => setActiveIndex(index)}
className={`sidebarListItem ${
index === activeIndex ? "active" : ""
}`}
>
{listItem}
</li>
);
})}
</ul>
when the user clicks on the item .. the activeIndex will be set as the item-index for the clicked item ..
and I am adding the className: "active" to the list-item only when the item-index === activeIndex

Related

How to call a function with arguments onclick of a button in a react component

Here is my function with arguments that i added in index.html in publics folder in a script tag
function displayContent(event, contentNameID) {
let content = document.getElementsByClassName("contentClass");
let totalCount = content.length;
for (let count = 0; count < totalCount; count++) {
content[count].style.display = "none";
}
let links = document.getElementsByClassName("linkClass");
totalLinks = links.length;
for (let count = 0; count < totalLinks; count++) {
links[count].classList.remove("active");
}
document.getElementById(contentNameID).style.display = "block";
event.currentTarget.classList.add("active");
}
Trying to call this function from click of buttons on my react component that looks like below
<button class="linkClass" onclick="displayContent(event, 'project2')">Meet at Campus
</button>
Please guide me with the syntax
Here's the correct syntax
<button className="linkClass" onClick={(event)=>displayContent(event,'project2')}>Meet at Campus</button>
Edit: please note that React components return JSX
It looks like you're trying to make some sort accordion but you shouldn't really be mixing vanilla JS with React as React needs control of the DOM.
So here's a brief example of how you might approach this using 1) state, and 2) a Panel component which comprises a button, and some content.
const { useState } = React;
function Example() {
// Initialise state with an array of false values
const [ state, setState ] = useState([
false, false, false
]);
// When a button in a panel is clicked get
// its id from the dataset, create a new array using `map`
// and then set the new state (at which point the component
// will render again
function handleClick(e) {
const { id } = e.target.dataset;
const updated = state.map((el, i) => {
if (i === id - 1) return true;
return false;
});
setState(updated);
}
// Pass in some props to each Panel component
return (
<div>
<Panel
name="Panel 1"
active={state[0]}
id="1"
handleClick={handleClick}
>
<span className="text1">Content 1</span>
</Panel>
<Panel
name="Panel 2"
active={state[1]}
id="2"
handleClick={handleClick}
>
<span className="text2">Content 2</span>
</Panel>
<Panel
name="Panel 3"
active={state[2]}
id="3"
handleClick={handleClick}
>
<span className="text3">Content 3</span>
</Panel>
</div>
);
}
function Panel(props) {
// Destructure those props
const {
name,
id,
active,
handleClick,
children
} = props;
// Return a div with a button, and
// content found in the children prop
// When the button is clicked the handler is
// called from the parent component, the state
// is updated, a new render is done. If the active prop
// is true show the content otherwise hide it
return (
<div className="panel">
<button data-id={id} onClick={handleClick}>
{name}
</button>
<div className={active && 'show'}>
{children}
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.panel button:hover { cursor: pointer; }
.panel { margin: 1em 0; }
.panel div { display: none; }
.panel div.show { display: block; margin: 1em 0; }
.add { margin-top: 1em; background-color: #44aa77; }
.text1 { color: darkblue; font-weight: 600; }
.text2 { color: darkgreen; font-weight: 700; }
.text3 { color: darkred; font-weight: 300; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Can't you use
document.getElementById("linkClass").onclick = () =>{
displayContent();
}
by giving the element an id with same of the class?

function in list only works on the first list item in react

I'm looping through a list, every list item has a arrow button that can show a hidden div underneath it. But right now every button triggers the first item in the list.
Im working right now in react, but i cant find the right solution
My JS react file
const CardConference = ({ content }) => {
const showItems = () => {
const items = document.getElementsByClassName("hidden-items")[0];
const arrow = document.getElementsByClassName("arrow-down")[0]
if (items.style.display == "block") {
items.style.display = "none";
arrow.classList.toggle('rotate-arrow')
} else {
items.style.display = "block";
arrow.classList.toggle('rotate-arrow')
}
}
return (
<div className="card card-conferences ">
<h4> {content[0].title} </h4>
<ul>
{content[0].cities.map((city) => (
<div>
<li className="city-name"> {city.name}
<button className="btn button-arrow" onClick={showItems} ><FaAngleDown color="#717171" className="arrow-down" /></button>
</li>
<ul className="hidden-items">
{city.conferenties.map((conf) => (
<li className="hidden-item">{conf} </li>
))}
</ul>
</div>
))}
</ul>
</div >
);
}
export default CardConference;
And this is my css
.arrow-down {
position: absolute;
margin-top: -11px;
margin-left: -6px;
transition: transform 1s;
}
.rotate-arrow {
transform: rotate(180deg);
}
..hidden-items {
display: none;
}
Because you only get getElementsByClassName for first element. You need to pass an index to get exactly the element.
const showItems = (i) => () => {
const items = document.getElementsByClassName("hidden-items")[i];
const arrow = document.getElementsByClassName("arrow-down")[i]
...
cities.map((city, index)
...
onClick={showItems(index)}
...

Prevent focus on Expand More button after content is inserted in React

I need to list out a long name list inside my page while showing all names at first is not desirable.
So I try to add an expand more button on it.
However, using a button will keep the browser focus on that button after it's pressed, left the button position unchanged on the screen while the name was inserted before that button.
On the other hand, using any, not focusable element (eg. div with onclick function) will do the desired behavior but lost the accessibility at all. Making the "button" only clickable but not focusable.
How do I make the button flushed to list bottom like the snippet div block does? Or is there a better choice to expand the existing list?
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Removing focus from the button in the click handler is probably the most elegant approach: e.target.blur(). It will work on any HTML element, whether it is focusable or not (as with the div in your case).
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
e.target.blur()
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Inspired by #MiKo, temporally unmount the button after click and set a timeout to add it back seems to do the work. Since browser lose the focus on original expand button, this will keep content flush down without focusing the original button:
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const [showBtn, setShowBtn] = React.useState(true)
const handleExpand = e => {
setShowBtn(false)
setIdx(idx + 1)
setTimeout(() => setShowBtn(true), 10)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
{showBtn?
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div> :
<div></div>
}
</div>
}
But I'm still looking a method that doesn't need to 'unmount' a thing which should be there all time.

Tabbed dates for api content

in React i am attempting to add my api content to a series of tabs, so you click on a tab, and it will show any listings in the api matching that date.
my attempt, code of component is below
import context from "./apiContext";
import styled from "styled-components";
const Tab = styled.button`
font-size: 20px;
padding: 10px 60px;
cursor: pointer;
opacity: 0.6;
background: white;
border: 0;
outline: 0;
${({ active }) =>
active &&
`
border-bottom: 2px solid black;
opacity: 1;
`}
`;
const ButtonGroup = styled.div`
display: flex;
`;
const tabs = ["18-08-20", "19-08-20", "20-08-20"];
function Movies() {
const { films } = useContext(context);
console.log(films);
const [active, setActive] = useState(tabs[0]);
return (
<>
<div className="movies">
<div className="title">
<h1>
Movies: <span />
All Releases
</h1>
<div className="tab-menu">
<ButtonGroup>
{tabs.map((tab) => (
<Tab
key={tab.label}
active={active === tab}
onClick={() => setActive(tab)}
>
{tab.label}
</Tab>
))}
</ButtonGroup>
</div>
</div>
<div className="content">
{films
.filter((item) => item.PreShowStartTime === Date.parse({ active }))
.map((item, index) => (
<div class="card" key={index}>
<div class="title">
<span class="title">{item.Title}</span>{" "}
<span>
{new Date(item.PreShowStartTime).toLocaleDateString("en", {
day: "2-digit",
month: "short"
})}
</span>
</div>
</div>
))}
</div>
</div>
</>
);
}
export default Movies;
To view with live API:
https://codesandbox.io/s/strange-hoover-8hik2
On line 24, is my test array of dates, which also should show as the label of the tab.
You are going to have to format the dates returned by the API according to what you have defined in the tabs constant in your code (or vice versa). Currently your API returns example 2020-08-18T14:00:00 but your tabs has format 18-08-20.
So for that, on your filter condition, you can use something like:
new Date(item.PreShowStartTime).toJSON().slice(2, 10) === active
where active is the active date selected on the tab
https://codesandbox.io/s/reverent-yonath-pv8i0?file=/src/Movies.js:1293-1357
As a side note, you were also accessing tab.label to print your tab labels. tabs does not contain a property named label...

How to design a multi tab chat with react/redux?

I am developing a chat platform to take multiple chats and he can switch between the chats.
var MessageList = React.createClass({
render() {
return (
<div className='messages'>
<h2> Conversation: </h2>
{
this.props.messages.map((message, i) => {
return (
<Message
key={i}
user={message.user}
text={message.text}
/>
);
})
}
</div>
);
}})
Let's just take an example of message list which will change when user switches the chat tab. Re rendering the same component with new messageList with respect to the chat makes sense but when there are 100 other component changes like this when there is a switch in chat, then there will be lot of repainting/rendering.(I know only diff will be changed in the dom but still.)
I would like to create different elements for different chats and hide and show them based on active chat. But react works under a single dom and replaces the dom with what's returned where it has been attached to.
React.render(<ChatApp/>, document.getElementById('app'));
Can anyone help me up with the design here?
Thanks in advance.
var Tabs = React.createClass({
displayName: 'Tabs',
propTypes: {
selected: React.PropTypes.number,
children: React.PropTypes.oneOfType([
React.PropTypes.array,
React.PropTypes.element
]).isRequired
},
getDefaultProps: function () {
return {
selected: 0
};
},
getInitialState: function () {
return {
selected: this.props.selected
};
},
shouldComponentUpdate(nextProps, nextState) {
return this.props !== nextProps || this.state !== nextState;
},
handleClick: function (index, event) {
event.preventDefault();
this.setState({
selected: index
});
},
_renderTitles: function () {
function labels(child, index) {
var activeClass = (this.state.selected === index ? 'active' : '');
return (
<li key={index}>
<a href="#"
className={activeClass}
onClick={this.handleClick.bind(this, index)}>
{child.props.label}
</a>
</li>
);
}
return (
<ul className="tabs__labels">
{this.props.children.map(labels.bind(this))}
</ul>
);
},
_renderContent: function () {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
},
render: function () {
return (
<div className="tabs">
{this._renderTitles()}
{this._renderContent()}
</div>
);
}
});
var Pane = React.createClass({
displayName: 'Pane',
propTypes: {
label: React.PropTypes.string.isRequired,
children: React.PropTypes.element.isRequired
},
render: function () {
return (
<div>
{this.props.children}
</div>
);
}
});
var App = React.createClass({
render: function () {
return (
<div>
<Tabs selected={0}>
<Pane label="Tab 1">
<div>This is my tab 1 contents!</div>
</Pane>
<Pane label="Tab 2">
<div>This is my tab 2 contents!</div>
</Pane>
<Pane label="Tab 3">
<div>This is my tab 3 contents!</div>
</Pane>
</Tabs>
</div>
);
}
});
ReactDOM.render(<App />, document.querySelector('.container'));
* {
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
body {
font: 300 14px/1.4 'Helvetica Neue', Helvetica, Arial, sans-serif;
background: #eee;
margin: 0;
padding: 0;
}
.tabs {
margin: 25px;
background: #fff;
border: 1px solid #e5e5e5;
border-radius: 3px;
}
.tabs__labels {
margin: 0;
padding: 0;
}
.tabs__labels li {
display: inline-block;
}
.tabs__labels li a {
padding: 8px 12px;
display: block;
color: #444;
text-decoration: none;
border-bottom: 2px solid #f5f5f5;
}
.tabs__labels li a.active {
border-bottom-color: #337ab7;
}
.tabs__content {
padding: 25px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div class="container"></div>
You are saying redux, so I'll try to give some insight but too lazy to provide any code, since it'll be too big / complex.
Beforehand, if needed, you can use normalizr when handling with nested JSON format since redux love to be immutable and nested makes it harder to be immutable.
The reducers:
chats { userid:"", message:"", time:"" },
users { userid:"", name:"" },
app { selectedUserId:"" }
Now, the number of tabs to be rendered / displayed is equals to number of users. The selected tab is based on app.selectedUserId. The message rendered in panel will be chats, which userid equals app.selectedUserId. Some snippet:
var lo = require('lodash');
var selectedChats = lo.filter(this.props.chats, k => k.userid == app.selectedUserId);
var messagesDom = selectedChats.map(k => <MessageLine chat={k});
var chatTabsDom = this.props.users.map(k => <ChatTab userid={k.userid} className={ k.userid == app.selectedUserId ? "active" : "" }> );
return <div>
<Tabs>{chatTabsDom}</Tabs>
<Messages>{messagesDom}</Messages>
</div>;

Categories