React useState not rendering with objects - reactjs

The Button Component has a event listener onClick which calls changeTheme(). The Content component is a div with some text which is passed a theme prop 'style'. When I click the button, the theme is changed for 2 consecutive clicks. After that no matter how many times I click the button, theme doesn't change.Why is that?
function ParentElement() {
const dark={
color:'white',
backgroundColor:'darkgray',
}
const light={
color:'darkgray',
backgroundColor:'white'
}
const [currentTheme,setTheme]=useState(light)
function changeTheme(e){
//toggle theme
currentTheme==light?setTheme(dark):setTheme(light)
}
return (
<div>
<Button changeTheme={changeTheme}/>
<Content style={currentTheme} />
</div>
)
}
function Content({style}) {
return (
<div style={style}>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
</div>
)
}

It stops working after two times because you are comparing object references, which are changing after the call to setState.
Since currentTheme is initialized to light, the first call to currentTheme == light ? setTheme(dark) : setTheme(light) goes in "the true branch" and sets currentTheme to dark. However setTheme — as any change of state object in React — returns a new object reference, so from this point onwards currentTheme reference will be different from the reference of light or even of dark, although at this point the theme is dark.
Therefore, from now the condition always go in "the false branch", which sets the theme to light.
In JavaScript, a comparison of objects as you are doing is a comparison of references.
You can fix the issue by e.g. comparing the theme name (a string), as it will be a comparison by value:
function ParentElement() {
const dark = {
name: 'dark',
style: {
color: 'white',
backgroundColor: 'darkgray'
}
}
const light = {
name: 'light',
style: {
color: 'darkgray',
backgroundColor: 'white'
}
}
const [currentTheme, setTheme] = useState(light)
function changeTheme(e) {
//toggle theme
currentTheme.name === light.name ? setTheme(dark) : setTheme(light)
console.log(currentTheme);
}
return (
<div>
<Button changeTheme={changeTheme}/>
<Content style={currentTheme.style} />
</div>
)
}

You can also do this way and archive this.
import React, { useState, useMemo } from "react";
const themes = {
dark: {
color: "white",
backgroundColor: "darkgray"
},
light: {
color: "darkgray",
backgroundColor: "white"
}
};
function ParentElement() {
const [themeMode, setThemeMode] = useState("light");
const changeTheme = (e) => {
//toggle theme
const newThemeMode = themeMode === "light" ? "dark" : "light";
setThemeMode(newThemeMode);
};
const currentTheme = useMemo(() => {
return themes[themeMode];
}, [themeMode]);
return (
<div>
<button onClick={changeTheme}>change Theme</button>
<Content style={currentTheme} />
</div>
);
}

Just in case you would like to understand what is actually happening here:
light is an object. When you set the default theme by useState(light) react sets a reference to the light object.
In your changeTheme handler, you are checking if the currentTheme and light are referencing the same object currentTheme==light, you are not checking if they are similar. That is in fact a huge difference here. On the first click, the outcome is true and the theme switches to dark.
On the second click, the theme switches to light but this time react is deep copying the light object under the hood. The reference to the light object is lost. Every time you check for currentTheme==light, the outcome will be false from now on, and the theme will never switch to dark again.
To solve this, you would have to make a deepEqual check (not recommended) or introduce another flag or variable to track the currently selected theme.

Reacts useState hook sets state asynchronous, which means it won't do it immediately. In this case, when you might want to use the useState callback. So try this:
currentTheme === light ? setTheme(() => dark) : setTheme(() => light)
I would also consider doing the check for current theme with the help of the callback, to make sure you compare with the actual state, like so:
setTheme((prevTheme) => prevTheme === light ? dark : light)
What I've learned is that when you want to modify your state based on the previous one, you want to take the previous state from the statehook callback, which makes sure you get the previous state.

first light will always be true since Boolean({}) will always resolve to true... you need to to check for example themes[key] and toggle based on that

Related

trying to add two seperate functions, each one have a button targeting one component react JS

im trying to give the jsx element a background, and i have two functions, function one gives the background gray and the second function gives it green.. so far i managed to add the two functions to the jsx elemnt through onClick however, when clicking both buttons it only run the green function
const [onTwo, SetTwo] = React.useState(false);
function toggleTwo() {
SetTwo((prevstate) => !prevstate);
}
const [green, setGreen] = React.useState(false);
const color = {
backgroundColor: green ? "#94D7A2" : "transparent",
};
function checkAnswers() {
setGreen(true);
}
return (
<h2
className="answer-two"
style={{ ...stylesTwo, ...color }}
onClick={() => {
toggleTwo();
checkAnswers();
}}
>
{props.answertwo}{" "}
</h2>
);
you set the background color, but in your case, I guess you need to toggle this function toggleTwo on every click and change that function to
function toggleTwo(){
SetTwo(!onTwo)
}
then that state will interchange between true and false every time u click
here is my working example I change one of backgroundColor to color to confirm its working,
https://codesandbox.io/s/wizardly-mclean-vgiukl?file=/src/App.js

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>;
}

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?

React: Get a list of tags by attribute values

I am trying to make a text which displays some information upon mouse hover. For example, I have three tags with following information
<div class="body main-seq" style="display: inline;">
<span prob="67.8">
Foo
</span>
<span prob="67.8;34.6">
Bar
</span>
<span prob="67.8;34.6;52.7">
Hello
</span>
</div>
On a browser, it will look something like this
FooBarHello
Basically, when user hovers a mouse on first bit of the text (the one that corresponds to "Bar"), I want to bold all the span tags that contain "34.6" in its "prob" attribute. In this case, it would have to bold "BarHello", but leave "Foo" as it is.
After doing some Google search, this task seems pretty trivial in Javascript or jQuery, and can be done by doing something like so,
$("span[prob*='34.6']") (along with onMouseOver event or something similar)
Find an element in DOM based on an attribute value
However, I've seen many posts saying I should absolutely try to avoid using jQuery in React because React and jQuery has conflicting philosophy (React renders DOM every time the data changes whereas jQuery directly manipulates DOM). Please correct me if I am wrong though.
So my question is, how can I achieve this in React?
You could perhaps do something like this in React:
import React, { useState } from 'react';
function MyComponent(props) {
const [ isHovered, setIsHovered ] = useState(false);
const onMouseEnter = () => {
setIsHovered(true);
}
const onMouseLeave = () => {
setIsHovered(false);
}
const spans = [
{ text: 'Foo', probs: [67.8] },
{ text: 'Bar', probs: [67.8, 34.6] },
{ text: 'Hello', probs: [67.8, 34.6, 52.7] }
];
return (
<div
class="body main-seq"
style="display: inline;"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{spans.map(({ text, probs }) => {
const isBold = isHovered && probs.includes(34.6);
return (
<span style={{ fontWeight: isBold ? 'bold' : 'normal'; }}>
{text}
</span>
);
}}
</div>
);
}
I hope this helps.
I think you're approaching this the wrong way, but it's a great exercise in "thinking in React".
You basically have these span tags that store number values. Each of them have the same responsibilities:
Store a list of number values
When clicked, set the new "criteria" values
let the others know that they should check if they should be bold or not
So let's call this component SpanComponent. We want a structure like this:
export const SpanComponent = ({ values, activeValues, setActiveValues, children }) => {
const isBold = checkForMatchingValues(values, activeValues) // returns true or false
const onMouseEnter = event => {
setActiveValues(values)
}
return (
<span style={{ fontWeight: isBold ? 'bold' : 'normal' }} onMouseEnter={onMouseEnter}>{children}</span>
)
}
Then in our main component we can manage these as so:
export const MainComponent = () => {
const [activeValues, setActiveValues] = useState([]) // empty array as default value
return (
<SpanComponent values={[68.8]} activeValues={activeValues} setActiveValues={setActiveValues}/>
<SpanComponent values={[68.8, 34.6]} activeValues={activeValues} setActiveValues={setActiveValues}/>
<SpanComponent values={[68.8, 34.6, 52.7]} activeValues={activeValues} setActiveValues={setActiveValues}/>
)
}

Toggling font awesome Icon using React

I want to toggle a fontAwesome icon class name on click. When clicked, the icon should change the color and also call a service which adds an object to a favorite list in the server (hence, why I use e.currentTarget: I need to remember which icon was clicked). This code works on the first click, but fails to change the class back on the second click (doing an inspect, it says the FA´s classname equals "Object object"). Any idea how I could fix it?
<FontAwesomeIcon onClick={this.ToggleClass} size={"sm"} icon={faHeart} />
ToggleClass = (e) => {
const heart = {
color: "#E4002B",
}
const clicked = {
color: "#E4002B",
background: "red"
}
if (e.currentTarget.className.baseVal != heart && e.currentTarget.className.baseVal != clicked) {
return e.currentTarget.className.baseVal === clicked;
#Callservicehere
}
else if (e.currentTarget.className.baseVal === clicked) {
e.currentTarget.className.baseVal = heart;
#callservicehere
}
}
You're not thinking in React yet :)
Accessing the event target and imperatively manipulating the DOM bypasses React's rendering - you might as well just be using jQuery. Not that there's anything bad about that, but it's not the right way to go about things in React.
In React, if you need to change the DOM in response to user interaction you do it in the render method, i.e. output different JSX based on the component's current state or props.
A couple things that might help here:
clicked and heart are both objects which means that you cannot compare them without using a deep comparison method.
var a = { id: 1 }
var b = { id: 1 }
console.log(a == b) //false
console.log(a === b) //false
If you want to compare them, you can convert them both to strings using the toString() method
heart.toString() === clicked.toString()
In your first if condition, it looks like you're returning a true/false value instead of assigning a desired classname to your target.
return e.currentTarget.className.baseVal === clicked // true/false
e.currentTarget.className.baseVal = clicked // assigned
You could also take the approach of keeping your classnames as strings and adding your styled objects inside of css
class MysteryComponent extends React.Component {
state = {
className: 'heart'
}
toggleClass = (e) => {
if (this.state.className === 'heart') {
this.setState({ className: 'clicked' })
} else if (this.state.className === 'clicked') {
this.setState({ className: 'heart' })
}
}
render() {
return (
<div className={this.state.className}>
<FontAwesomeIcon onClick={this.toggleClass} size={"sm"} icon={faHeart} />
</div>
)
}
}
// css
.heart {
color: "#E4002B";
}
.clicked {
color: "#E4002B";
background: "red";
}
I see. You want to fill/unfill the color of the heart as the user clicks. The reason why the results are not meeting your expectations is because of the event.targets are especially funky with FontAwesome. You may think you're clicking on it, but it manipulates the DOM in a way that when you try extract the className, it's value is often inconsistent.
This is why everyone is recommending that you make use of React's state. The logic that determines how elements are styled is now more controlled by the component itself instead of the FontAwesome library. Consider the code below, we only care about whether the item was clicked, not what class it initially has.
class Example extends React.Component{
state = {
clicked: false
}
handleOnCLick = () => {
this.setState({
clicked: !this.state.clicked
})
}
render(){
var clicked = this.state.clicked
return(
<button onClick={this.handleOnClick}>
<i
class={ clicked ? "fas fa-heart" : "fas fa-circle"}
</i>
</button>
)
}
}

Resources