React style object not being applied - javascript

Having issues with React style not being applied. I have no idea why it is not working as it was before.
See code below:
Accordion.js
import React, {useState} from 'react'
import { risk_assessment } from '../data/questions';
import AccordionItem from '../components/AccordionItem';
import Question from '../components/Question';
const Accordion = props => {
const [active, setActive] = useState("0")
return (
<ul className="accordion">
{risk_assessment.map((question, index) => (
<AccordionItem
key={index}
itemTitle={question.question}
itemContent={<Question options={question.options} name={question.name} />}
toggle={() => setActive(index)}
active={active == index} />
))}
</ul>
)
}
export default Accordion
AccordionItem.js
import React, {useRef, useEffect} from 'react'
const AccordionItem = ({ itemTitle, itemContent, toggle, active }) => {
const accordionContent = useRef()
let contentHeight = {}
useEffect(() => {
contentHeight = active ? {height: accordionContent.current.scrollHeight} : {height: "0px"}
})
return (
<li className="accordion_item">
<button className="button" onClick={toggle}>
{itemTitle}
<span className="control">{active ? "—" : "+"}</span>
</button>
<div className="answer_wrapper" ref={accordionContent} style={contentHeight} >
<div className="answer">{itemContent}</div>
</div>
</li>
)
}
export default AccordionItem
Question.js simply renders the data inside the Accordion Item.
Here is the output from Chrome developer tools.
I have tried messing with the useEffect hook to no success. Changed it to run on every render, only on the first render, added the ref as a dependency etc.
I need to use the useRef hook to get the height of the content area dynamically.
Any help would be appreciated.

In your case when the component re-renders the value of your variable will be lost. Try putting contentHeight in a state.
const [contentHeight, setContentHeight] = useState({})
useEffect(() => {
setContentHeight(active ? {height: accordionContent.current.scrollHeight} : {height: "0px"});
}, [active])
You can find more information in this post.

Related

How to make element scrolled to bottom in React.js?

In my app I want to make my element always scrolled to bottom after getting new logs.
For some reason my logsRef.current.scrollTop has value of zero all the time. My logs do show on screen and in console. I am not sure why is this not working, I've tried to use different approaches using useLyaoutEffect() but nothing made logsRef.current.scrollTop value change, it stayed zero all the time.
//my Logs.jsx component
import { useEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`}>
<div ref={logsRef}>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also, I do render my Logs.jsx in BattlePhase.jsx component where I do my attack logic on click and I save logs using useState() hook.
//parts where i do save my logs in BattlePhase.jsx
const [logs, setLogs] = useState([]);
const attackHandler = () => {
//logs where pokemon on left attacked pokemon on right
setLogs((prevLogs) => [
...prevLogs,
`${pokemonDataOne.name} attacked ${
pokemonDataTwo.name
} for ${attack.toFixed(2)} dmg`,
`${pokemonDataTwo.name} died`,
])
}
...
<Attack className={isActiveArrow}>
<Button onClick={attackHandler}>Attack!</Button>
</Attack>
Slight long shot but it's possible that the ref is attached to the wrong element. Are you sure the element with the CSS property that makes it scrollable (overflow) isn't on <Container>?
//my Logs.jsx component
import { useLayoutEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useLayoutEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`} ref={logsRef}>
<div>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also to confirm, you do need useLayoutEffect here.

Is it possible to use css transition in React?

For example I have this code.
And I want to use CSS transitionfor Button when showButton and when !showButton. Now it's just removed and add Button when showButton changes.
{showButton && (
<Button
onClick={() => setShowMessage(true)}
size="lg"
>
Show Message
</Button>
)}
Is it possible make by some events or appending classNames like active?
Append the className with the ternary operator.
But, for example, this code will only adjust the class of the button specified (effectively doing the same thing you described, hiding & showing the button):
import React, { useState } from 'react';
export const Component = () => {
const [showButton, setShowButton] = useState(false);
const handleClick = () => {
setShowButton(true);
}
return (
<button
onClick={handleClick}
className={showButton ? 'showButtonClass' : 'hideButtonClass'}
>
Show Message
</button>
);
};
For content to show once the button is clicked, you'll need something like:
import React, { useState } from 'react';
export const Component = () => {
const [showMessage, setShowMessage] = useState(false);
const handleClick = () => {
setShowMessage(true);
}
return (
<div>
<button
onClick={handleClick}
>
Show Message
</button>
{showMessage && <h1>
The message you'll see when clicking!
</h1>}
</div>
);
};

React - toggle text and class in an HTML element?

I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox

how to cleanup the useEffect function in react js after removing component

I am getting this error while using useRef and useEffect in react js.
**how can i cleanup the useEffect in React js this is main topic of this all question **
Dropdown.js:9
Uncaught TypeError: Cannot read properties of null (reading 'contains')
at HTMLDocument.bodydroptoggler (Dropdown.js:9)
here is screenshot:
I am getting this error when i click on the button named as "drop toggler"
here is code of app.js
import React, { useState } from "react";
import Dropdown from "./components/Dropdown";
const options = [
{
label: "red color is selected",
value: "red",
},
{
label: "blue color is selected",
value: "blue",
},
{
label: "green color is seleted",
value: "green",
},
];
const App = () => {
const [dropactive, setDropactive] = useState(true);
return (
<div className="container ui">
<button
className="button ui"
onClick={() => setDropactive(!dropactive)}
>
drop toggler
</button>
{dropactive ? <Dropdown options={options} /> : null}
</div>
);
};
export default App;
and here is code of dropdown.js
import React, { useState, useRef, useEffect } from "react";
const Dropdown = ({ options }) => {
const [selected, setSelected] = useState(options[0]);
const [open, setOpen] = useState(false);
const ref = useRef();
useEffect(() => {
const bodydroptoggler = (event) => {
if (ref.current.contains(event.target)) {
return;
}
setOpen(false);
};
document.addEventListener("click", bodydroptoggler);
return () => {
document.removeEventListener("click", bodydroptoggler);
console.log("work");
};
}, []);
const RenderedOptions = options.map((option, index) => {
if (selected.value === option.value) {
return null;
} else {
return (
<div
className="item"
key={index}
onClick={() => {
setSelected(option);
}}
>
{option.label}
</div>
);
}
});
return (
<div ref={ref} className="ui form">
<div className="field">
<label className="text label">Select from here:</label>
<div
className={`ui selection dropdown ${
open ? "active visible" : ""
}`}
onClick={() => setOpen(!open)}
>
<i className="dropdown icon"></i>
<div className="text">{selected.label}</div>
<div className={`menu ${open ? "visible transition" : ""}`}>
{RenderedOptions}
</div>
</div>
</div>
</div>
);
};
export default Dropdown;
here is what i want to perform
i just want to hide that form by clicking on the button.
how you can run this project
just create a react app
put code of app.js to app.js of your project
dropdown.js inside the component folder
i hope this all detail will help you i you need anything more just commnet down
thanks in advance
Have you tried using optional chaining since ref.current might sometimes be undefined?
if (ref.current?.contains(event.target))
Here's a codesandbox link with the fix.
Also some additional context from React Ref docs on why sometimes the ref might be null
React will assign the current property with the DOM element when the component mounts, and assign it back to null when it unmounts.
EDIT:
This is whay useLayoutEffect is for. It runs it's contents (and cleanups) synchronously and avoids the race condition. Here's the stackblitz that proves it:
https://stackblitz.com/edit/react-5w7vog
Check out this post from Kent Dodd's as well:
One other situation you might want to use useLayoutEffect instead of useEffect is if you're updating a value (like a ref) and you want to make sure it's up-to-date before any other code runs. For example:
ORIGINAL ANSWER
It's complicated. Your code generally looks good so it took me a minute to understand why. But here's the why - the Dropdown component unmounts before the cleanup from the effect is run. So the click event still finds the handler, this time with a null reference for the ref (because the ref gets updated immediately).
Your code is correct, idomatic React - but this is an edge case that needs deeper understanding.
As the other answerer already mentioned, just add an optional check. But I thought you might like to know why.

React functional component with mapped Child functional component with onClick function

Hi I have mapped some json data named "projectsData" and I am trying to "bind" an onClick event with a setState hook. The mapping works except for the "onClick" does not work when clicking the grid item. In my case I want to update filterproject value with the project.id value from that target.
Right now when I click an item it does nothing.
How do I successfully map a function to "onClick" while using functional components?
Below is the parent Component
import React, { useEffect, useState } from "react";
import projectsData from '../data/projectsData';
import Project from './Projects';
const App = (props) => {
const [projects] = useState(() => (projectsData.map((project) => <Project id={project.id} project={project} onClick={() => {setFilterProject(project.id)}}/>)));
const [filterproject, setFilterProject] = useState(null);
return (
<body>
<div id='sepLine'>
<div id="visHolder">
<div id="visContainer" style={{position: "relative", width: "840px", height: "1823px"}} >
{projects}
</div>
</div>
</div>
</body>
);
}
export default App;
And here is the Child Component - "Project"
import React, { useRef } from "react";
const Project = (props) => {
const {projectClick, project} = props;
return (
<div className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`} style={{positon: "absolute"}} onClick={projectClick}>
<h5>{project.title}</h5>
<br></br>
<p className="year">
<span className="yearsstart">{project.start}</span> - <span className="yearsend">{project.end}</span>
<br></br>
<span className="kind">{project.kind}</span>
</p>
</div>
)
}
export default Project
below is a screen grab of Console showing one of the mapped projects and it's onClick parameters. I can see it but when I click nothing happens. Any help would be great!
You pass click handler to a prop called onClick when setting initial state
const [projects] = useState(() => projectsData.map((project) => (
<Project
id={project.id}
project={project}
onClick={() => {setFilterProject(project.id)}}
/>
));
but access it as projectClick in the component
const { projectClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={projectClick}
>
...
</div>
Fix by accessing the correct prop
const { onClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={onClick}
>
...
</div>

Categories