I've been trying to add a menu with an active styling by using useState in React js but for some reasons it only stays in active state, does not go back to inactive when you click on the other menu item. On the other hand, it works perfectly when I pass down my active state as props from Sidebar.js
React version: "react": "^17.0.2"
Node version: v16.0.0
Here is my code:
SideLink.js
import React, {useState} from "react";
const SideLink = ({ name, Icon}) => {
const [active, setactive] = useState("Home")
return (
<li className="group" onClick={() => setactive(name)}>
<a href={name.toLowerCase()} className="cursor-pointer block mb-2 pointer-events-none">
<div className="inline-block">
<div
className={`
flex items-center
group-hover:bg-blue-50
group-hover:text-red-500
rounded-full pl-2 py-3 pr-6
${active === name ? "text-red-400" : ""}
`}
>
<Icon />
<span className="ml-2 font-bold text-xl">{name}</span>
</div>
</div>
</a>
</li>
);
};
export default SideLink;
Sidebar.js
import React, {useState} from "react";
import SideLink from "../../assets/components/SideLink";
import {
BookmarksIcon,
ExploreIcon,
HomeIcon,
ListsIcon,
MessagesIcon,
MoreIcon,
NotificationsIcon,
ProfileIcon,
} from "../../assets/icons/Icon";
const sideLinks = [
{
name: "Home",
icon: HomeIcon,
},
{
name: "Explore",
icon: ExploreIcon,
},
{
name: "Notifications",
icon: NotificationsIcon,
},
{
name: "Messages",
icon: MessagesIcon,
},
{
name: "Bookmarks",
icon: BookmarksIcon,
},
{
name: "Lists",
icon: ListsIcon,
},
{
name: "Profile",
icon: ProfileIcon,
},
{
name: "More",
icon: MoreIcon,
},
];
const Sidebar = () => {
return (
<div className="w-72 flex flex-col justify-between px-2">
<div>
<nav>
<ul>
{sideLinks.map(({ name, icon }) => (
<SideLink key={name} name={name} Icon={icon}/>
))}
</ul>
</nav>
</div>
<div>bottom</div>
</div>
);
};
export default Sidebar;
Thank you!
Keep in mind that in your example each SideLink maintain their own independent state. When one of those call its own setactive, it doesn't have any effect on any other.
What I think you really want is one piece of state that live in a central location, the parent component, Sidebar, and forward to SideLink that value and the means to change that it.
Related
I am somewhat new to react and I am building a navigation menu where I target the ul and li components using the map() function with index to avoid duplicating code. Whenever I click to open one component all components open instead of an individual one. I know the issue is probably in me not targeting the components correctly so any help will be appreciated.
Here is the code:
...react icon and state imports
const Navbar = () => {
const [subMenuOpen, setSubmenuOpen] = useState(false);
const menu = [
{
title: "Management",
submenu: true,
icon: <FaUserTie/>,
submenuItems: [
{ title: "MGMT Cockpit" },
{ title: "P&L by Month" },
{ title: "B/O Report" },
{ title: "User List" },
],
},
{
title: "Tools",
submenu: true,
icon: <BsTools/>,
submenuItems: [
{ title: "Inventory" },
{ title: "Damages" },
{ title: "MGMGT Tools" },
{ title: "Plan Tools" },
{ title: "Sales Tools" },
{ title: "Planning Tools" },
]
},
];
return (
<div className="flex">
<div className="bg-primary h-screen w-64 p-3 relative overflow-y-scroll">
{menu.map((item, index) => <sidebarItem key={index} sidebarItem = {index}/>)}
<ul className="pt-2">
{menu.map((menu, index) => (
<>
<li className="
text-gray-300 text-lg flex item-center gap-x-4 cursor-pointer p-2 my-4
hover:bg-slate-50 hover:text-primary hover:rounded-lg duration-500"
onClick={() => setSubmenuOpen(!subMenuOpen)}> //Fix this???
<span className="text-2xl block float-left">
{menu.icon ? menu.icon : <RiDashboardFill/>}
</span>
<span className="text-base font-medium flex-1">{menu.title}</span>
{menu.submenu && (
<BsChevronDown className={`${subMenuOpen && "rotate-180"} duration-300`}/>
)}
</li>
{menu.submenu && subMenuOpen && (
<ul>
{menu.submenuItems.map((submenuItem, index) => (
<li key={index} className="text-gray-300 text-md gap-x-4 px-5 my-3">
{submenuItem.title}
</li>
))}
</ul>
)}
</>
))}
</ul>
</div>
</div>
)
}
export default Navbar
App.js just imports this Navbar so I didn't include the code for that.
You have different possibilities of open submenus (since you have multiple menu types), so you should have state that reflects that. A plain true/false state won't be able to store enough information. One approach would be an array of submenu indices that are open.
const [submenuIndicesOpen, setSubmenuIndicesOpen] = useState([]);
Then examine the indices when iterating to determine what should be shown and how to call setSubmenuItemsOpen when toggling.
<ul className="pt-2">
{menu.map((menuItem, index) => ( /* don't shadow the menu variable here */
<>
<li className="
text-gray-300 text-lg flex item-center gap-x-4 cursor-pointer p-2 my-4
hover:bg-slate-50 hover:text-primary hover:rounded-lg duration-500"
onClick={() => setSubmenuOpen(
submenuIndicesOpen.includes(index)
? submenuIndicesOpen.filter(i => i !== index)
: [...submenuIndicesOpen, index]
)}>
{menuItem.submenu && submenuIndicesOpen.includes(index) && (
If only one submenu can be open at a time, you could instead have a state that's just a single number, the index of the open submenu.
your subMenuOpen state should be an array with index and in onClick handler you should pass index of li that you have in map , to control which menu should be open
or
you can set your menu as a state with a property of open on each submenu to control open of it
I'm looking to record the input files in one object to use the details in a graph. Everything works great but the first character I input it only generates the empty object thus the last character I input is not recorded in the object.
How can I get around this?
Project: https://github.com/fauxir/expense_chart_component
main Comp:
import SpendingGraph from "./SpendingGraph";
import ThisMonth from "./ThisMonth";
import WeekInput from "./WeekInput";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { appear } from "/Users/Fauxir/Desktop/CSS-HTML-JS-REACT practice/expense_chart_component/src/redux/showSlice";
function MainComp() {
const [values, setValues] = useState({});
const see = useSelector((state) => state.show.value);
const dispatch = useDispatch();
const onChange = (e) => {
setValues({ ...values, [e.target.placeholder]: e.target.value });
//setDaySpending({...daySpending, [e.target.id]: e.target.value })
console.log(values);
//console.log(daySpending)
};
const inputs = [
{ placeholder: "Spent Monday", id: 1 },
{ placeholder: "Spent Tuesday", id: 2 },
{ placeholder: "Spent Wednesday", id: 3 },
{ placeholder: "Spent Thursday", id: 4 },
{ placeholder: "Spent Friday", id: 5 },
{ placeholder: "Spent Saturday", id: 6 },
{ placeholder: "Spent Sunday", id: 7 },
];
return (
<div className="bg-bridal-heath-500 w-80 h-fit rounded-lg flex flex-col items-center justify-between">
{!see
? inputs.map((input) => (
<WeekInput key={input.id} {...input} onInput={onChange} />
))
: null}
{see ? <SpendingGraph /> : null}
{see ? <ThisMonth /> : null}
<input onBlur={onChange}></input>
{!see ? (
<div
onClick={() => dispatch(appear())}
className="h-12 w-72 bg-terracotta-600 flex flex-row items-center justify-center rounded-md cursor-pointer mt-6 hover:bg-terracotta-500 hover:shadow-sambuca-300 hover:shadow-lg mb-8"
>
<div>Calculate</div>
</div>
) : null}
</div>
);
}
export default MainComp;
Rendered input comp:
function WeekInput(props) {
const { placeholder, onChange, ...inputProps } = props;
return (
<div className="h-8 w-2/4 flex flex-col justify-center mt-4">
<input
className="bg-bridal-heath-500 leading-none rounded-sm shadow-sm focus:outline-none focus:shadow-outline text-gray-600 font-medium border-sambuca-300 border-2"
placeholder={placeholder}
{...inputProps}
onChange={onChange}
></input>
</div>
);
}
export default WeekInput;
You are passing the onChange function as props named onInput and using it as onChange.
So you have to change the name of props onInput to onChange like below.
// MainComp
<WeekInput key={input.id} {...input} onChange={onChange} />
Update
Add useEffect to synchronize state.
// MainComp
useEffect(() => {
setValues(values);
}, [values]);
FYI
Changing state for input is delayed by one character (useState hook)
I'm doing a chat widget (a tab system where I have multiple tabs, each tab being a chat) and I'm trying to paint some sort of UX notification every time the user gets a new message from the backend (enduser). In order to do that what I'm trying to do is to emit a custom method from my child component onUpdate lifecycle hook that will trigger the parent component event that will add a class to my parent component in order to add a red border every time the final user send a new message.
This is my child component:
<template>
<div class="flex-col flex-nowrap">
<BaseTranslationBar />
<div id="chat-body" ref="chatBody" class="overflow-y-auto p-2">
<ul id="chatBox" class="mt-4">
<BaseChatMessage
v-for="(chatMessage, index) in chatSession.events"
:chatMessage="chatMessage"
:key="index"
:chatSession="chatSession"
:class="{ 'mt-0': index === 0, 'mt-4': index > 0 }"
/>
</ul>
</div>
<form #submit.prevent="sendMessage" class="chat-form relative mt-6">
<textarea
class="chat-textarea h-[160px] py-4"
placeholder="Add your message here..."
type="text"
v-model="message"
/>
<div
class="chat-input-buttons flex justify-end absolute bottom-0 right-0 pb-4"
>
<button
class="flex flex-col justify-center focus:outline-none focus:ring"
>
<i class="chat-input-button fa-paperclip fas text-blue-500"></i>
</button>
<button
type="submit"
value="Send"
class="flex flex-col justify-center focus:outline-none focus:ring"
>
<i class="chat-input-button fa-paper-plane fas text-blue-500"></i>
</button>
</div>
</form>
<BasePythiaBar class="mt-4" />
<BaseChatButtonRow :chatId="chatSession.id" class="mt-4" />
</div>
</template>
<script>
import { ref, onMounted, onUpdated } from "vue";
import ChatService from "#/services/ChatService.js";
import BaseChatMessage from "#/components/BaseChatMessage.vue";
import BaseTranslationBar from "#/components/BaseTranslationBar.vue";
import BasePythiaBar from "#/components/BasePythiaBar.vue";
import BaseChatButtonRow from "#/components/BaseChatButtonRow.vue";
export default {
emits: ["emittingChatTabUxState"],
components: {
BaseChatMessage,
BaseChatButtonRow,
BasePythiaBar,
BaseTranslationBar,
},
props: {
chatSession: {
type: Object,
default: function () {
return {};
},
},
},
setup(props, { emit }) {
const message = ref("");
const chatBody = ref(null);
onMounted(() => {
scrollChatBodyToEnd();
});
onUpdated(() => {
scrollChatBodyToEnd();
submitChatTabUxState();
});
const scrollChatBodyToEnd = () => {
if (chatBody.value) {
chatBody.value.scrollTop = chatBody.value.scrollHeight;
}
};
const submitChatTabUxState = () => {
emit("emittingChatTabUxState");
};
const sendMessage = () => {
const messageObject = {
aggregateId: props.chatSession.id,
message: message.value,
agentId: props.chatSession.agentId,
skippedValidationErrors: [],
};
ChatService.sendChatMessage(messageObject);
message.value = "";
};
return {
message,
sendMessage,
chatBody,
scrollChatBodyToEnd,
submitChatTabUxState,
};
},
};
</script>
As you can see everytime the dom of this component gets updated I'm emitting a custom event called "submitChatTabUxState" to the parent.
This is the parent component:
<template>
<div>
<ul
class="tag-menu flex space-x-2"
:class="defaultTagMenu ? 'default' : 'historic'"
role="tablist"
aria-label="Tabs Menu"
v-if="tabTitles && tabTitles.length"
>
<li
#click.stop.prevent="selectedTitle = title"
v-for="title in tabTitles"
:key="title"
:title="title"
role="presentation"
:class="{ selected: title === selectedTitle }"
>
<a href="#" role="tab" :class="updateChatClassValue"
#emittingChatTabUxState="updateChatClass()">
{{ title }}
</a>
</li>
</ul>
<slot />
</div>
</template>
<script>
import {
ref,
computed,
useSlots,
provide,
watch,
onMounted,
onUpdate,
} from "vue";
export default {
props: {
defaultTagMenu: {
type: Boolean,
default: true,
},
},
setup() {
const updateChatClassValue = ref("");
const slots = useSlots();
const tabTitles = computed(() =>
slots.default()[0].children.map((tab) => tab.props.title)
);
const tabTitlesLength = computed(() => tabTitles.value.length);
let selectedTitle = ref(tabTitles.value[0]);
provide("selectedTitle", selectedTitle);
provide("tabTitles", tabTitles);
watch(tabTitlesLength, (currentValue, oldValue) => {
if (currentValue < oldValue) {
selectedTitle.value = tabTitles.value[0];
}
});
onMounted(() => {
updateChatClass();
timing();
});
onUpdate(() => {
console.log("updating parent component");
});
const timing = () => {
setInterval(() => {
updateChatClassValue.value = "";
}, 2000);
};
const updateChatClass = () => {
console.log("Updating chat class");
updateChatClassValue.value = "chatUpdated";
};
return {
tabTitles,
selectedTitle,
tabTitlesLength,
timing,
updateChatClass,
updateChatClassValue,
};
},
};
</script>
Now what is confusing to me at this point is where to trigger the emitted event coming from my child component in the parent component. I don't have a specific call to action to do this because I will need to trigger the custom event in my parent component every time the child component gets updated so I don't know where should I place something like #emittingChatTabUxState="updateChatClass()". I'm adding it at the <a> tag at the moment but it's not working.
I am trying to render the TEXT CLOUD in my Nextjs app, but it is getting rendered infinite time.I am using Next JS. Please help
Here is the code and screenshots:
import dynamic from "next/dynamic";
import { useRef} from "react";
const container = '.tagcloud';
const texts = [
'3D', 'TagCloud', 'JavaScript',
'CSS3', 'Animation', 'Interactive',
'Mouse', 'Rolling', 'Sphere',
'6KB', 'v2.x',
];
const tag = {
radius: 100,
maxSpeed: 'normal',
initSpeed: 'fast',
direction: 135,
keep: true
}
const Text_cloud=dynamic(
() => {
const refTag=useRef();
return <span ref={refTag}>{TagCloud(container,texts,tag)}</span>;
},
{ ssr: false }
);
export default Text_cloud;
I am exporting this Component and using it in another page skills:
import React, { useState,useRef } from "react";
import Web from "../Component/web";
import Programming from "../Component/programming";
import Editing from "../Component/Editing";
import styles from "../styles/skill.module.css"
import Btn from "../Component/btn";
import Text_cloud from "../Component/text_cloud";
function Skill() {
const [skill, setskill] = useState("Web")
return (<>
<div className={styles.main}>
<ul className={styles.ruler}>
<a onClick={() => setskill("Web")} className={styles.skilla}><Btn name="Web Dev" /></a>
<a onClick={() => setskill("Programming")} className={styles.skilla}><Btn name="Programming" /></a>
<a onClick={() => setskill("Editing")} className={styles.skilla}><Btn name="Editing" /></a>
</ul>
<div className={styles.cld} >
<div className="tagcloud" >
<Text_cloud />
</div>
</div>
</div>
{skill === "Web" && <Web />}
{skill === "Programming" && <Programming />}
{skill === "Editing" && <Editing />}
</>)
}
export default Skill;
After running the server I get this output and error:
It get render two times
And the error it shows is:
Here is the error Screenshot
Please help me out here
I am facing a bit of a problem. I have two components. One parent component that is called goals and under different instances of goal. I am updating state of the parent component by passing a function to its children. The state of the parent is updated however only one of the child who made the change apply the change they should be all the same
Goals
import UserGoal from "./UserGoal";
import { Button, Container, Form, InputGroup } from "react-bootstrap";
import React, { useState, useEffect } from "react";
import "./UserGoal.css";
import { Link } from "react-router-dom";
const UserGoals = () => {
var test = [
{
id: 1,
name: "Marriage",
icon: "Marriage.png",
isSelected:false,
style: "goal-category"
},
{
id: 2,
name: "Car",
icon: "Car.png",
isSelected:false,
style: "goal-category"
},
{
id: 3,
name: "Home",
icon: "Home.png",
isSelected:false,
style: "goal-category"
},
{
id: 4,
name: "Education",
icon: "Education.png",
isSelected:false,
style: "goal-category"
},
{
id: 5,
name: "Travel",
icon: "Travel.png",
isSelected:false,
style: "goal-category"
},
{
id: 6,
name: "Retirement",
icon: "Retirement.png",
isSelected:false,
style: "goal-category"
},
{
id: 7,
name: "Other",
icon: "Other.png",
isSelected:false,
style: "goal-category"
},
]
const [goals, setGoals] = useState(test)
const changeSelection = (id) => {
const newarr = goals.map((GoalType)=> {
if(GoalType.id===id){
GoalType.isSelected=true
GoalType.style= "goal-category2"
}
else {
GoalType.isSelected=false
GoalType.style= "goal-category"
}
return GoalType
})
forceUpdate(newarr)
console.log(goals);
}
const forceUpdate = React.useCallback((arr) => {
setGoals(arr);
}, []);
return (
<Container>
<h1>Your saving goal</h1>
<div className="goal-categories">
{goals.map((GoalType) => (
<UserGoal
id={GoalType.id}
name={GoalType.name}
icon={GoalType.icon}
isSelected={()=>GoalType.isSelected}
style={()=>GoalType.style}
key={GoalType.id}
onClick={changeSelection}
></UserGoal>
))}
</div>
<div className="reg-wrapper">
<div className="reg-inner">
<Form className="ctr-form">
<Form.Label>Amount</Form.Label>
<InputGroup className="mb-3">
<Form.Control
className="input"
type="number"
placeholder="0.00"
/>
<div className="input-group-text">SAR</div>
</InputGroup>
<Form.Label>Date</Form.Label>
<InputGroup className="mb-3">
<Form.Control className="input" type="date" />
</InputGroup>
<div className="btn">
<Link to="/expenses">
<Button id="btn btn-primary" className="dbtn">
Next
</Button>
</Link>
</div>
</Form>
</div>
</div>
</Container>
);
}
export default UserGoals
Goal
import React from "react";
import { useState } from "react";
import "./UserGoal.css";
import { useEffect } from "react";
export default function UserGoal({ id, name, icon, isSelected, style, onClick }) {
const [style_, setStyle_] = useState(style())
return (
<>
<div className="goal-category-container">
<div
onClick={()=>{
onClick(id);
setStyle_(style())
}}
className={style_}
>
<div className="icon-wrap">
<img src={"assets/" + icon} alt="Goal Icon" />
</div>
</div>
<div className="category-name">{name} </div>
</div>
</>
);
}
You call setStyle_ immediately after the callback, even though the effects of the callback are asynchronous. Ie, the style won't be updated yet because the patent is doing an asynchronous setState.
Instead send down style as a value, not a function. That way the child will react to updates. Also having style as state oh the child is unnecessary, since you can just rely on the prop.
Also, don't use mutation in your map function. Instead create new goal objects. And your React.useCallback is equivalent to using the setter directly, it can be removed.