React Toggle Functionality - reactjs

Current Project Image
I have a Content component that renders 2 Panel components. The Content component passes its state to panel 1 and passes !state to panel 2. (Panel 1 is initially visible and panel 2 is not)
Desired Functionality:
When the user clicks the "hidden" component, it sets it to visible and hides the visible one.
If the user clicks the now visible component, nothing happens but if the now hidden component is clicked, it should become visible and the visible one goes back to being hidden.
Content.jsx
import React, { useState } from "react";
import Panel from "../panel/Panel";
import "./content.styles.css";
const Content = ({ selected, panelA, panelB }) => {
const [active, setActive] = useState(true);
return (
<div className={`content-wrapper ${selected ? "visible" : "hidden"}`}>
<Panel active={active} panelText={panelA} setActive={setActive} />
<Panel active={!active} panelText={panelB} setActive={setActive} />
</div>
);
};
export default Content;
Panel.jsx
import React from "react";
import "./panel.styles.css";
const Panel = ({ panelText, active, setActive }) => {
const handleClick = () => {
if(!active){
setActive(false);
}
};
return (
<div
onClick={handleClick}
className={`panel-wrapper ${
active ? "active-component" : "hidden-component"
}`}
>
{panelText}
</div>
);
};
export default Panel;
I really hope I'm just missing something or I'm not understanding how React is working under the hood. I really appreciate any help or advice you could give me to get me going down the right path.
Thank you so much!

If I understood your question correctly, this codesandbox that I created should help.
Issues
You are almost there, but you have a couple of issues here:
Using only one state variable for both child components.
Since you are setting active to true and false, and passing the same state variable to both panels, panels will affect each other.
Setting active to false if panel isn't active
const handleClick = () => {
if(!active){
setActive(false); // If the panel is not active, no reason to setActive(false)
}
};
Solution
You need to have separate identifiers for panels, that is why I used an array to keep track of active panels (if you have only two a string will also be fine)
const [activePanels, setActivePanels] = useState([/* initial active panels */])
You can add and remove to this array based on your panel's condition:
const handleClick = () => {
if (!active) setActivePanels([panelText]);
};

Related

React Element not re-rendering on setState when state is passed

I'm trying to implement a simple dark/light theme toggle to my website. In my base App.tsx I've implemented the state I use:
const [colorScheme, setColorScheme] = useState("light");
I pass that "colorScheme" variable as a prop to every other element. The theme toggle is contained in a header element, so I also pass the "setColorScheme" function to header as a prop. Within Header.tsx, the code triggered when the toggle is clicked is:
setColorScheme(s => s === "dark" ? "light" : "dark");
Within every specific element, I set the color scheme like so:
<ElementName className={"element_name element_name_"+colorScheme}/>
I have all the css for styling the component contained in the class "element_name", and then all relevant color data is contained in "element_name_light" or "element_name_dark".
When the toggle in the header is clicked, a re-render is triggered for the main body of the app, and for the header. But all of the other elements do not re-render. If I navigate to another element, the re-render happens and the color scheme appears as intended.
Attached is a gif of this happening.
I'm still learning React, so I'm sure it's something obvious I'm missing. I would appreciate any tips anyone can provide! Thanks
One note: I am using react functionally, rather than implementing classes for each component.
It's impossible to tell exactly what mistake you made since you haven't shared your code. But I can tell you the root mistake is not using React's context API. This will allow you to hold the color scheme and the toggle function as a global state and import them into every component via the useContext hook.
Here's an example on stackblitz: https://stackblitz.com/edit/react-ts-lhwstv?file=color-scheme-ctx.tsx
Here's the docs: https://reactjs.org/docs/context.html
Note: I'm using typescript, if you're using plain javascript just remove the type declarations and the generic typings <Type>.
You start by creating the context and giving a default value:
type ColorScheme = 'light' | 'dark';
type Props = { colorScheme: ColorScheme; toggleColorScheme: () => void };
export const ColorSchemeCtx = createContext<Props>({
colorScheme: 'light',
toggleColorScheme: () => {},
});
I like to then create a provider component for organization.
export const ColorSchemeCtxProvider: FC<PropsWithChildren<{}>> = ({
children,
}) => {
const [colorScheme, setColorScheme] = useState<ColorScheme>('light');
function toggleColorScheme() {
setColorScheme((s) => (s === 'dark' ? 'light' : 'dark'));
}
return (
<ColorSchemeCtx.Provider value={{ colorScheme, toggleColorScheme }}>
{children}
</ColorSchemeCtx.Provider>
);
};
Then wrap all components that need the context - probably just put it at the highest level possible.
root.render(
<StrictMode>
<ColorSchemeCtxProvider>
<App />
</ColorSchemeCtxProvider>
</StrictMode>
);
Now any component can get both the color scheme and / or the toggle function with useContext
export default function App() {
const { colorScheme, toggleColorScheme } = useContext(ColorSchemeCtx);
return (
<div>
<p>The color scheme is: {colorScheme}</p>
<button onClick={toggleColorScheme}>TOGGLE</button>
<CompOne />
<CompTwo />
<CompThree />
</div>
);
}
export default function CompOne() {
const { colorScheme } = useContext(ColorSchemeCtx);
return <div className={'comp-one ' + colorScheme}></div>;
}

React toggle hyperlink of grid header

I am working with react with Web API. I need one solution where I wanted to have one Hyperlink for once of the headers of Grid. And once I click's over the hyperlink, I wanted to hide this hyperlink and show other hyperlink (toggle). For Ex-
Those Hyperlinks are not directing anywhere. These are in use to select all Radiobuttons and another Hyperlink is used for unselecting all the Radiobuttons for Grid rows.
It's easy to toggle. But it's a hyperlink, which makes it a little tricky. Does the hyperlink links to anywhere?
Anyway, you can toggle the href in onClick, in order for 'href' take effect before 'href' itself is changed, use setTimeout (10 milliseconds should be enough):
import React from 'react'
const ToggleLinks = () => {
const google = {href:"https://google.ca", text:"google"};
const yahoo = {href:"https://yahoo.ca", text:"yahoo"};
const [link, setLink] = React.useState(google);
const toggleLink = e => {
console.log('clicked')
console.log(e.target)
setTimeout(() => {
setLink(p => {
return p.href === "https://google.ca" ? yahoo : google;
})
}, 10);
}
return (
<div>
<h1>ToggleLinks</h1>
{link.text}
</div>
)
}
export default ToggleLinks

useEffect triggered after onClick event - with different results

I wanted to create a dropdown menu, which shows itself and hides on hovering, and disappears after clicking its item. I thought I found a way to do it - but it works only sometimes. (Or maybe it doesn't work - but sometimes it does.) Details below:
I gotta DropdownMenu2 component, which display is being toggled by onMouseEnter/Leave events. This component (my dropdown menu) holds inside <NavLink> menu items.
I wanted the dropdown menu to disappear after clicking on menu item, so inside <Navlink> I created onClick event which triggers handleClick. This functions sets a click variable - to a CSS className with display:none. click is then passed to <div> that contains the Dropdown menu.
To toggle the dropdown menu display again on mouse hover, I had to get rid of the click class from the div. For that I created useEffect hook, with click dependency - so it fires every time click state changes. And function inside this hook - changes click value, so it no longer represents the CSS display:none class. So after (2.) - div containing dropdown menu has display:none, disapears, and useEffect erases that - making it hover ready.
problem:
this works only sometimes - sometimes useEffect is triggered so fast after onClick, that the dropdown menu doesn't even drop. ( click changes so fast that div container gets the "erasing" class immediately after display:none class )
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
import useHoverButton from "./sub-components/useHoverButton";
const NaviMainButtonDrop2 = () => {
const { disp, hoverOn, hoverOff } = useHoverButton();
return (
<li
className={`nav-main__button dropdown-us`}
>
<a
className="hover-pointer"
onMouseEnter={hoverOn}
onMouseLeave={hoverOff}
>
title
</a>
{ disp && <DropdownMenu2 /> }
</li>
)
}
export default NaviMainButtonDrop2
useHoverButton (custom hook for NaviMainButtonDrop2)
import { useState } from "react";
const useHoverButton = () => {
const [disp, setDisp] = useState(false);
const hoverOn = () => setDisp(true)
const hoverOff = () => setDisp(false)
return { disp, hoverOn, hoverOff }
}
export default useHoverButton
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
import { useEffect } from "react";
import useAddClass from "./sub-components/useAddClass";
const DropdownMenu2 = () => {
const { click, setClick, handleClick } = useAddClass("hide-menu");
useEffect(() => {
console.log("[usEffect]")
setClick("");
}, [click]);
return (
<div className={`dropdown-holder-us ${click}`}>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
onClick={handleClick}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
useAddClass (custom hook for DropdownMenu2)
import { useState } from "react"
const useAddClass = (className) => {
const [click, setClick] = useState("");
const handleClick = () => setClick(className);
return { click , handleClick }
}
export default useAddClass
I think the issue here is that you are not able to get the latest state whenever you update the next state that is why it works sometimes and sometimes it doesn't.
According to me there could be 2 solutions to this, either use a setTimeout or get the latest state when setting the state.
setTimeout solution-
useEffect(() => {
setTimeout(() => {
setClick("")
},2000)
Try and always get the latest state when you update the next state.
useEffect(() => {
console.log("[usEffect]")
setClick((clickLatest) => "");
}, [click]);
and
const handleClick = () => setClick((clickLatest) => className);
This callback will help the useState wait for the latest state and then update the state further.
I think I just found a simple solution to this. I don't understand why useEffect seems to work in a random timing, but using setTimeOut inside it, and delaying the execution of setClick - seems to do the job.
useEffect(() => {
setTimeout(() => {
setClick("")
},2000)

React app slows down and JS Heap gets bigger. How to find memory leak?

I'll try to simplify this as much as possible. I have a "create an avatar" page which contains a "color chooser" component. Each time you click it a function generates a new random color and that color is then fed back into the props of the color chooser component so it can be used as the background color.
If I click the button as fast as I can it swaps the colors quickly at first but then starts to slow down to the point where the app becomes unusable, even if I navigate to a new page.
Dev tools shows that the JS heap and nodes count just keeps getting bigger and bigger and I can't stop this happening however much I simplify it.
Here is a minimal exmaple of my page component and my color chooser component.
Page
const Component: React.FC<Props> = (props) => {
const [mode, setMode] = useState('avatar')
const [avatarColor, setAvatarColor] = useState(initialColor)
// Color Clicked
const colorClicked = useCallback(() => {
let newColor = pickRandomColor()
setAvatarColor(newColor as string)
}, [])
return (
<StyledPage {...props}>
{mode === 'avatar' &&
<AvatarColorChooser
color={avatarColor}
onClick={colorClicked}
/>
}
</StyledPage>
)
}
export default Component
const StyledPage = styled.div`
// Styles
`
Color Chooser
const Component: React.FC<Props> = (props) => {
return (
<StyledComponent color={props.color}>
<button type="button" onClick={props.onClick}>
<Icon iconName="paintbrush" />
</button>
</StyledComponent>
)
}
export default Component
const StyledComponent = styled.div<StyledProps>`
// Styles
`
So, It look like this only happens in dev mode. I built the site and clicked as fast as I could for over a minute and no slowdown. The profiler shows that nodes and event listeners are being released often and the JS heap is a sawtooth pattern. I got the idea from this discussion. github.com/facebook/react/issues/12141

ReactJS how to 'reset states' of a mapped array of components, on click of one of components in array

I will use the analogy of a 'light bulb like' on / off state throughout. A text field is considered on (or active) when it is blue, and off when it is black.
I currently have an array of text, that I map through, and the values get passed to a component.
This component manages the local state of the text, for each text on click it will flick the color blue basically (on / off)
I have a global state that I would like to be true if any item is blue. My issue is that if 'text_one' is on, and I click 'text_two', i'd effectively like 'text_one' to turn off, and 'text_two' turn on, and continue having the global state on. Essentially, only one text may be blue at a time. If you click on a text component, it will turn blue, and turn the rest off, if they are blue.
verbally, the logic (I assume) would be
if(global && local) then blue
if (global && !local) then black
If I understand react correctly, if a state changes, it should re-render all the Text Components. Using onClick on the component, I'm able to change the state of whatever I click, but I'm unsure how I would change the state of the rest of them to change them back to black.
My Source code is below, and can be found on codesandbox.io.
https://codesandbox.io/s/distracted-violet-tc3lh?fontsize=14&hidenavigation=1&theme=dark
import React, { useState, useEffect } from "react";
import "./styles.css";
const App = () => {
const [texts, setTexts] = useState(["text_one", "text_two"]);
const [globalActive, setGlobalActive] = useState(false);
const TextEntry = ({ value }) => {
const [localActive, setLocalActive] = useState(false);
return (
<h1
onClick={() => {
setLocalActive(!localActive);
setGlobalActive(true);
}}
style={{
color: localActive && globalActive ? "blue" : "black"
}}
>
{value}
</h1>
);
};
return (
<div className="App">
{texts.map((text, index) => {
return <TextEntry value={text} />;
})}
</div>
);
};
export default App;
Strange logic imo. Just indentify which element you click and do a comparison.
Check it out
https://codesandbox.io/s/vibrant-morse-pj7ro?fontsize=14&hidenavigation=1&theme=dark
Is it what you wanted?

Resources