React - one state for managing 'active' flag on multiple compontents - reactjs

I have a list of compontents I want to share the same state.
I want, at any given time, only the last selected component to have the active state whilst the others to be 'inactive'.
codesandbox link
Thanks.
EDIT:
for SO future proofing of potential dead links:
SidebarList Compontent:
const [active, setActive] = useState(false);
const handleActive = (i) => {
console.log(i);
i.currentTarget.className = active ? "a" : "b";
console.log(i.currentTarget.className);
};
const list = props.items.map((item) => {
return (
<ListItem key={item.id} handleActive={(item) => handleActive(item)}>
{item.text}{" "}
</ListItem>
);
});
return (
<div className="sidebar__list">
<h2>{props.title}</h2>
<ul>{list}</ul>
</div>
);
}
ListItem compontent
function List_item(props) {
return (
<li onClick={props.handleActive}>
<a>
<h2>{props.children}</h2>
</a>
</li>
);
}
export default List_item;
And the App component:
export default function App() {
return (
<div className="App">
<SidebarList
title="About"
items={[
{ id: 1, text: "Item 1 arr" },
{ id: 2, text: "Item 2 arr" }
]}
/>
<SidebarList
title="Templates"
items={[
{ id: 1, text: "Item 1 arr" },
{ id: 2, text: "Item 2 arr" }
]}
/>
</div>
);
}

TLDR; react router v4 NavLink perform singular maintenance of one active className, dependant on the current routed page.
NavLink property 'activeClassName' will parse a specfied className to the current routed page, whilst removing on non-routed pages. All state management in this question is therefore redundant.

Related

Sharing data/state between 2 sibling components without rerendering

I am building a web app with next.js and am using the PowerBI client to render an iframe with statistics. I can interact with the iframe via the library to update filters and pages. This means the iframe should NOT rerender everytime a prop changes.
The parent component looks like this.
const Dashboard: NextPage = ({ data }) => {
const [activePage, setActivePage] = useState(null);
const DynamicDashboard = dynamic(
() =>
import("../components/dynamicDashboard").then(
(dashboard) => dashboard.default
),
{ ssr: false }
);
return (
<>
<Header setActivePage={setActivePage} />
<DynamicDashboard data={data} activePage={activePage} />
</>
);
};
The header (child 1) looks like this:
export default function ({ setActivePage }) {
const menuItems = [
{
displayName: "page1",
name: "ReportSection5ac5ce426571489b0038",
},
{
displayName: "page2",
name: "ReportSectionf3695be54d17769ba015",
},
{
displayName: "page3",
name: "ReportSection60bb35142132a98c6b86",
},
];
const menuItemClickHandler = (name) => {
setActivePage(name);
};
return (
<>
<header className={styles.header}>
<div className={styles.inner}>
<div className={styles.logo}>
<img src="/assets/images/logo.svg" alt="logo" />
</div>
<ul className={styles.menu}>
{menuItems.map((item) => (
<li key={item.name}>
<a href="#" onClick={() => menuItemClickHandler(item.name)}>
{item.displayName}
</a>
</li>
))}
</ul>
</div>
</header>
</>
);
}
DynamicDashboard (child 2) receives the prop "activePage" like this.
To keep everything simple and focussed on the question I'll keep the contents of this component empty.
export default function DynamicDashboard({ data, activePage }) {}
I understand that the rerender happens because I'm using useState.
However when I want to use useRef I get the message that I'm not allowed to use a ref on a function component.
So basically I want to update the DynamicDashboard component based on events in the Header component without rerendering the whole component.

Export pdf in kendo react exports only the last item inside map

I'm trying to export as a pdf a few items, where every item has a button that should export as a pdf a specific item. I also tried to give a key to each child, but it didn't work.
Here's my code:
import './Logout.css';
import React, { useRef } from "react";
import { NavLink } from 'react-router-dom';
import { PDFExport } from '#progress/kendo-react-pdf';
const Logout = () => {
const data = [
{ id: 1, name: 'Item 1', value: 100 },
{ id: 2, name: 'Item 2', value: 200 },
{ id: 3, name: 'Item 3', value: 300 },
];
const pdfExport = useRef(null);
const exportPDF = () => {
pdfExport.current.save();
};
return (
<div className='logOutStyle'>
<h1> you just loged out</h1>
<h3> to sing in again click here</h3>
{data.map((item, i) => (
<div key={item.id}>
<PDFExport key={item.id} ref={pdfExport} paperSize="A4" margin="2cm">
<p key={data[i]}>{data[i].name}</p>
</PDFExport>
<button key={item.id} onClick={exportPDF}>Export PDF</button>
</div>
))}
<NavLink to="/" className="underline text-tertiary">
return to home page
</NavLink>
</div>
)
}
export default Logout;
There is no relevant and updated answer for that in kendo react docs or chat gpt.
Please help me.
I did research on all over kendo react docs, chat gpt.
I hope someone will wake up and see the huge problem.

Mapping links to components

I'm learning to react and wanted to create a card that, when clicked, routes the user to an external web page. I haven't seen any tutorials or explanations on this all I keep finding is for internal website navigation. I tried doing this in different ways but can't find the correct way to do this.
my component:
import React from "react";
function Card (props) {
const handleClick = () => {
return
}
return (
<div>
}
<div className="link-container" onClick= {()=> {
return handleClick();}
}>
<div className="row">
<div className="card">
<hr className="divide"></hr>
<img className="img" src={props.img} alt="social-icon" />
<h4 className="name">{props.name}</h4>
</div>
</div>
</div>
</div>
)
}
export default Card;
href links to be used:
const links = [
{
id: 1,
name: "Youtube",
img:"./img/youtube1.svg",
href: "https://www.youtube.com/c/SubwaySounds"
},
{
id: 2,
name: "Spotify",
img:"./img/spotify.svg",
href: "https://artists.spotify.com/c/artist/3DM32kjsG7Pp5EM7o3Uv7t/profile/overview"
},
{
id: 3,
name: "Tiktok",
img:"./img/tiktok.svg",
href: "https://www.tiktok.com/#nysubwaysounds"
},
{
id: 4,
name: "Instagram",
img:"./img/Instagram1.svg",
href: "https://www.instagram.com/nycsubwaysounds/?hl=en"
},
{
id: 5,
name: "Shop",
img:"./img/shop.svg",
href: "https://my-store-11524143.creator-spring.com/"
}
]
export default links;
What you need to do is to create an element for each item for the list. For this, you can use the builtin JS map function.
For example, you can create a new variable holding the elements:
const linksElements = links.map(item => (<Card item={item} />));
and use it with {linksElements} inside of the return value.
I've created this as an example using your list: https://stackblitz.com/edit/react-yt1hfl?file=src/App.js. Note that this is a quick and dirty implementation, without any other components.

React - Map dynamic component name within map loop

I'm new to react and trying to create a menu bar of sorts. All the similar questions seem to be older or not quite what I need. Regardless, I can't get this to work so please help figuring this out.
I'm using react-icons and would like to dynamically name the component so I can use the icons within this loop but <{val.icon} /> is throwing an error: "'...' expected." What can I do to make this work? I also can't seem to declare variables in a return statement to work around this, or at least I can't figure out how to do so. Any idea?
const values = [
{ id: 1, text: "Home", icon: "MdHomeFilled" },
{ id: 2, text: "Schedule", icon: "MdEditCalendar" },
{ id: 3, text: "Scores", icon: "MdSportsTennis" },
{ id: 4, text: "Stats", icon: "IoIosStats" }
];
return (
<>
<ul>
{values.map((val) => (
<li onClick={() => setActiveId(val.id)}>
<div className={activeId === val.id ? "NavLinkBox selected":"NavLinkBox"}>
<div className="navLinkBoxHdr"><{val.icon} /></div>
<div className="navLinkBoxCnt">{val.text}</div>
</div>
</li>
))}
</ul>
You can't just use a component name (i.e. as a string) in order to render a component.
In order to render it, you need to import it like you would regularly, then convert it into a variable with a capitalized name. For example:
import MdHomeFilled from './MdHomeFilled';
import MdEditCalendar from './MdEditCalendar';
export default function App() {
const values = [
{ id: 1, text: "Home", icon: MdHomeFilled },
{ id: 2, text: "Schedule", icon: MdEditCalendar }
];
return (
<ul>
{values.map((val) => {
const IconComponent = val.icon; // This way you can use this field as a component
return (
<li key={val.id} onClick={() => setActiveId(val.id)}>
<div className={activeId === val.id ? "NavLinkBox selected":"NavLinkBox"}>
<div className="navLinkBoxHdr"><IconComponent /></div>
<div className="navLinkBoxCnt">{val.text}</div>
</div>
</li>
);
})}
</ul>
);
}

Update list of displayed components on deletion in React

in the beginning on my path with React I'm creating simple to-do app where user can add/remove task which are basically separate components.
I create tasks using:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
I render list of components (tasks) using following method:
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask = {(id) => this.removeTask(id)}
key = {index}/>;
})
);
}
method to remove specific task takes unique ID of task as an argument and based on this ID I remove it from the tasks list:
removeTask(uID){
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != uID )
}));
}
But the problem is, when I delete any item but the last one, it seems like the actual list of components is the same only different objects are passed to those components.
For example:
Lets imagine I have 2 created componentes, if I set state.Name = 'Foo' on the first one, and state.Name='Bar' on the second one. If I click on remove button on the first one, the object associated to this component is removed, the second one becomes first but it's state.Name is now 'Foo' instead of 'Bar'.
I think I'm missing something there with correct creation/removing/displaying components in react.
Edit:
Method used to remove clicked component:
removeCurrentTask(){
this.props.removeTask(this.props.taskObj.id);
}
SingleTask component:
class SingleTask extends Component{
constructor(props) {
super(props);
this.state={
showMenu : false,
afterInit : false,
id: Math.random()*100
}
this.toggleMenu = this.toggleMenu.bind(this);
}
toggleMenu(){
this.setState({showMenu : !this.state.showMenu, afterInit : true});
}
render(){
return(
<MDBRow>
<MDBCard className="singleTaskContainer">
<MDBCardTitle>
<div class="priorityBadge">
</div>
</MDBCardTitle>
<MDBCardBody className="singleTaskBody">
<div className="singleTaskMenuContainer">
<a href="#" onClick={this.toggleMenu}>
<i className="align-middle material-icons">menu</i>
</a>
<div className={classNames('singleTaskMenuButtonsContainer animated',
{'show fadeInRight' : this.state.showMenu},
{'hideElement' : !this.state.showMenu},
{'fadeOutLeft' : !this.state.showMenu && this.state.afterInit})}>
<a
title="Remove task"
onClick={this.props.removeTask.bind(null, this.props.taskObj.id)}
className={
classNames(
'float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightRed'
)
}
>
<i className="align-middle material-icons">remove</i>
</a>
<a title="Edit title"
className={classNames('show float-right btn-floating btn-smallx waves-effect waves-light listMenuBtn lightBlue')}
>
<i className="align-middle material-icons">edit</i>
</a>
</div>
</div>
{this.props.taskObj.description}
<br/>
{this.state.id}
</MDBCardBody>
</MDBCard>
</MDBRow>
);
}
}
Below visual representation of error, image on the left is pre-deletion and on the right is post-deletion. While card with "22" was deleted the component itself wasn't deleted, only another object was passed to it.
Just to clarify, the solution was simpler than expected.
In
const showTasks = () => taskList.map((item, index) => (
<SingleTask
taskObj={item}
removeTask ={removeTask}
key = {item.id}
/>
)
)
I was passing map index as a key, when I changed it to {item.id} everything works as expected.
In short, in the statement tasksList.push(<SingleTask taskObj={taskObj} removeTask ={this.removeTask}/>);, removeTask = {this.removeTask} should become removeTask = {() => this.removeTask(taskObj.id)}.
However, I would reconsider the way the methods addTask and showTasks are written. While the way you have written isn't wrong, it is semantically unsound. Here's what I would do:
addTask(taskObj){
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks(){
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
const SingleTask = (task) => {
const { taskObj } = task;
return <div onClick={task.removeTask}>
{ taskObj.title }
</div>
}
// Example class component
class App extends React.Component {
state = {
tasksList: [
{ id: 1, title: "One" },
{ id: 2, title: "Two" },
{ id: 3, title: "Three" },
{ id: 4, title: "Four" }
]
}
addTask = (taskObj) => {
let tasksList = this.state.tasksList;
tasksList.push(taskObj);
this.setState({tasksList : tasksList});
}
showTasks = () => {
return (
this.state.tasksList.map((item, index) => {
return <SingleTask
key={index}
taskObj={item}
removeTask ={() => this.removeTask(item.id)}/>;
})
);
}
removeTask(id) {
this.setState(prevState => ({
tasksList: prevState.tasksList.filter(el => el.id != id )
}));
}
render() {
return (
<div className="App">
<div> {this.showTasks()} </div>
</div>
);
}
}
// Render it
ReactDOM.render(
<App />,
document.body
);
<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>

Resources