reactstrap, callback hook used as ref weird behaviour - reactjs

While trying to get a DOM element's position on render using this code:
const Modes = () => {
const callbackRef = useCallback(domNode => {
if (domNode) {
let rect = domNode.getBoundingClientRect()
console.log("rect", rect)
}
}, []);
return <>
<Toast>
<ToastHeader></ToastHeader>
<ToastBody>
<div ref={callbackRef}> </div>
</ToastBody>
</Toast>
</>
}
I noticed that it always prints a DOMRect object with zero for every property :
If I add state dependence and then state changes causing rerender, the correct position will be printed. Something like this:
const Modes = () => {
const callbackRef = useCallback(domNode => {
if (domNode) {
let rect = domNode.getBoundingClientRect()
console.log("rect", rect)
}
}, []);
const [show, setShow] = useState(true) // added state
return <>
<Toast>
<ToastHeader></ToastHeader>
<ToastBody>
{show ? <div ref={callbackRef}> </div> : null} // div inside Toast can be toggled
</ToastBody>
</Toast>
<Button onClick={() => setShow(!show)} >toggle </Button> // added toggle button
</>
}
After double click on the button:
What confuses me the most is the fact that if I replace this Toast imported from Reactstrap with pure html with bootstrap classes the problem disappears. And this is exactly what React renders because I copied it from source code in the browser:
<div class="toast fade show" role="alert">
<div class="toast-header">
<strong class="me-auto"></strong>
</div>
<div class="toast-body">
<div ref={callbackRef}> </div>
</div>
</div>
And it seems to be a problem that exists just for this Toast component. For Reactrstrap's Card for example it is not the case. So how can using a component which at the end of the day gets rendered into a certain html code be different from using the same html code and why this particular component turns out to be a special case regarding obtaining its DOMRect?

Related

Why Click event got unwired when we append an element in React functional components

I tried to trigger the click event of a button which was append into the body. but the click event got unwired when we appending an element in the React Functional components..please find the code below,
Code snippet:
const [value, setValue] = React.useState(0);
function increaseValue() {
console.log('click');
setValue(value + 1);
}
useEffect(() => {
var btnElement = document.getElementById('btn');
document.body.appendChild(btnElement);
}, []);
return (
<div className="control-pane">
<div className="control-section modal-dialog-target">
<div id="btn">
<button onClick={increaseValue}>Click</button>
<span>{value}</span>
</div>
</div>
</div>
);
If i remove the document.body.appendChild(btnElement); The click event works fine.
Sample: https://stackblitz.com/edit/react-ntrkyi-lefq4u?file=index.js,package.json,index.html
How can i fix this issue?
To be honest I'm not 100% sure what you're trying to do in the useEffect() but your button isn't working, can be updated to:
<button
type="button"
onClick={() => {
increaseValue();
}}
>
Click
</button>
That'll get the increment working.

(Theme Toggle) How can I make className sync dynamically to active localStorage value from its key when toggled in React

className "page" needs to be modified to "page light-theme" or "page dark-theme" on toggle through local storage key "theme-color" with values of light-theme and dark-theme.
The active key value does change in local Storage but updates only show if the pages is refreshed. I need the changes to sync on toggle
Page to be changed
export default function Page({children}){
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
{children}
</div>
)
}
h3 inner text needs to change dynamically depending on the active value from key "theme-color" in local storage. I have place the variable "themeOpener" in between h3 tag. No changes take place
heres is my code
export default function Body() {
let themeOpener;
if (`${localStorage.getItem('theme-color','light-theme')}`) {
themeOpener = "🧛🏼Ahh the light it burns! Please use toggle, I prefer dark mode!";
} else {
themeOpener = "I learnt to design in React and im hooked 🤩";
}
return (
<div className="body">
{/* <h3 id="opener">I learnt to design in React and im hooked 🤩</h3> */}
<h3 id="opener">{themeOpener}</h3>
</div>
);
}
This is code for my toggle where local storage is created
const ToggleMode = () => {
// state
const [isLight, setIsLight] = useState(false);
// effect
useEffect(() => {
// check local storage
const CurrentTheme = localStorage.getItem("theme-color");
if (CurrentTheme === "light-theme") {
setIsLight(true);
} else {
setIsLight(false);
}
console.log(useEffect);
}, []);
const ToggleChecked = () => {
// logic
if (isLight) {
localStorage.setItem("theme-color", "dark-theme");
setIsLight(false);
} else {
localStorage.setItem("theme-color", "light-theme");
setIsLight(true);
}
console.log(ToggleChecked);
};
return (
<div className="toggle--container">
<input
type={"checkbox"}
id="toggle"
className="toggle--checkbox"
checked={isLight}
onChange={ToggleChecked}
/>
<label htmlFor="toggle" className="toggle--label">
<span className="toggle--label-background"></span>
</label>
<div className=""></div>
</div>
);
};
export default ToggleMode;
Yes, this is natural according to your code. Whenever toggling, ToggleMode component will only be re-rendered by changed state value isLight.
But your Body and Page component which are supposed to be ToggleMode's parent will not be re-rendered. Because their props or states never changed by ToggleChecked().
To get it done working, you need to do something to re-render parents in Page and Body component.
How? You need to create a state value in those components or create IsLight and setIsLight at the top level component. And then these two would be drilled into ToggleMode.
Something like followings.
export default function Page({children}){
// state
const [isLight, setIsLight] = useState(false);
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
<Body isLight={isLight} setIsLight={setIsLight} />
{children}
</div>
)
}
export default function Body({isLight, setIsLight}) {
return (
<div>
<ToggleMode isLight={isLight} setIsLight={setIsLight} />
</div>
);
}
export default function ToggleMode ({isLight, setIsLight}){
// This is not needed anymore.
// const [isLight, setIsLight] = useState(false);
return (<>Your toggle code...</>)
}
P.S. Don't you think this is quite irritating? To avoid prop drilling, we use state management utilities such as react context API or 3rd party libraries such as Redux.

state is not updating when the component re renders?

I'm making a Nextjs flashcard app. I'm passing a deck structure like this:
const deck = {
title: 'React 101',
flashcards: [flashcardOne, flashcardTwo],
};
as props to the Deck component. This component shows the first card in flashcards and a "next" button to increment the index and showing the next card in flashcards.
The Card component is very simple and shows the front and the back of the card depending of the state front.
This is what I got so far and it's working but if I click "next" when the card is showing the answer (flashcard.back), the next card is going to appear with the answer. And I'm not sure why, isn't the Card component re rendering when I click "next"? And if the component re renders, front is going to be set to true?
export default function Deck({ deck }) {
const [cardIndex, setCardIndex] = useState(0);
const { title, flashcards } = deck;
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>{title}</h1>
{cardIndex < flashcards.length ? (
<>
<div className={styles.grid}>
<Card flashcard={flashcards[cardIndex]} />
</div>
<button onClick={() => setCardIndex((cardIndex) => cardIndex + 1)}>
Next
</button>
</>
) : (
<>
<div>End</div>
<button>
<Link href='/'>
<a>Go to Home</a>
</Link>
</button>
<button onClick={() => setCardIndex(0)}>Play again</button>
</>
)}
</main>
</div>
);
}
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
return (
<>
{front ? (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(false)}
>
<p className={styles.front}>{flashcard.front}</p>
</div>
) : (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(true)}
>
<p className={styles.back}>{flashcard.back}</p>
</div>
)}
</>
);
}
When state changes, the card will re-render, but it will not re-mount. So, existing state will not be reset.
Call setFront(true) when the flashcard prop has changed:
const [front, setFront] = useState(true);
useLayoutEffect(() => {
setFront(true);
}, [flashcard]);
I'm using useLayoutEffect instead of useEffect to ensure front gets set ASAP, rather than after a paint cycle (which could cause flickering).
You can also significantly slim down the Card JSX:
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
const face = front ? 'front' : 'back';
return (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(!front)}
>
<p className={styles[face]}>{flashcard[face]}</p>
</div>
);
}
Okay, I guess I had the same issue. Since you're using functional components, and you're re-using the same component or in better words, you're not unmounting and remounting the component really, you're just changing the props, this happens. For this, you need to do useEffect() and then setFront(true).
Here's the code I used in my App.
useEffect(() => {
setFront(true);
}, [flashcard]);
This is what I have used in my Word.js file.

How to create a div on click of a button in react?

I've just started learning React. I'm working on a project for practice. It has two different components Nav & Main.I'm calling these components in App.js. My Nav component has a number of coloured divs which show up on clicking the plus icon. Now, I want to create a new div in Main.js on click of a coloured div in Nav.js. This new div should have the same background colour as the one we click on. I'm confused how to do it. Please! Help me out.
This is my Nav component.
const Nav = () => {
const [showColors, setShowColors] = useState(false);
return(
<div className="NavBar">
<h1 className="NavHeading">Notes.</h1>
<div onClick={() => setShowColors(!showColors)} className="PlusImg"></div>
<div className={showColors ? "" : "ColorBar"}>
<div className="Color Color1"></div>
<div className="Color Color2"></div>
<div className="Color Color3"></div>
<div className="Color Color4"></div>
<div className="Color Color5"></div>
</div>
</div>
);
};
export default Nav;
This is my Main component.
const Main = () => {
return(
<div className="Main">
<div className="NoteItem"></div>
</div>
);
};
export default Main;
I want to create new div with class name of NoteItem as the one written in the Main component.
I'm not 100% sure I understand what you're trying to do. Apologies if I've misunderstood.
I'd approach this by keeping track of the items that have been added in component state:
const App = () => {
// starting with an empty array
const [items, setItems] = React.useState([]);
And defining a handler for adding an item:
const App = () => {
const [items, setItems] = React.useState([]);
// itemClass is just a string, e.g. "color1" or "color5"
const addItem = itemClass => setItems([...items, itemClass]);
The spread syntax used above creates a new array containing the previous array plus the new item:
const oldArr = ['color1','color2'];
const newArr = [...oldArr, 'color3']
// newArr is now ['color1', 'color2', 'color3'];
You can then pass the items to Main as a prop, which can render a div for each item:
const App = () => {
const [items, setItems] = React.useState([]);
const addItem = itemClass => setItems([...items, itemClass]);
return (
<Main items={items} /> {/* pass items array to Main */}
);
}
const Main = ({items}) => { // {items} is the equivalent of props.items
// render a div for each item in the items array
return items.map( item => (
<div className={item}>This item has a class of {item}</div>
)
}
And pass the addItem handler to Nav so it can tell App to add the clicked item:
const Nav = ({addItem}) => {
return (
<div className={showColors ? "" : "ColorBar"}>
<div onClick={() => addItem('Color1')} className="Color Color1"></div>
{/* repeat for Color2, etc. */}
</div>
);
}
With this place, when a Nav div is clicked an item will get added to App's items array, which will trigger a re-render, passing the updated array to Main, and you'll see the new div.
a bit of housekeeping:
In the Main component above you're going to get a react key warning. I omitted the key in the interest of readability, but you'll need to include a key prop that's unique for each item. The easiest way to do this is to just use the index of the iteration:
const Main = ({items}) => {
// add key={index} to make react happy
return items.map((item, index) => (
<div key={index} className={item}>This item has a class of {item}</div>
)
}
Also, the items.map call will blow up if items isn't provided. You can dodge this by setting it to an empty array by default:
const Main = ({items = []}) => {
// ...
}
First of all you need to define your state in your App component and pass it to your components as an argument because both your Nav and Main components needs to access your state. You can do it like this
class App extends React.Component{
constructor(){
super()
this.state = {
showColors: false
}
}
}
After that you can take elements color with the onClick event listener. Define it in your state as well. And create a div element inside your Main component with it.
Dont forget to pass it as an argument to your Main component too.

Unexpected behaviour with rendering a list of buttons in react

I am trying to generate a list of buttons using this method. I rewrote it as a test instance and the behaviour is the same - when you render the buttons, the function in onClick is called and cannot be called again by clicking the generated buttons. In this case 1 to 5 are logged.
function App() {
const [buttonList, setButtonList] = useState();
const experimental = async (e) => {
const array = [1,2,3,4,5];
const listItems = array.map((item) =>
<li key={item}>
<button onClick={console.log(item)}>This is a test</button>
</li>
);
setButtonList(listItems);
}
return (
<div className="App">
<header className="App-header">
<button onClick={experimental}>Click to render</button>
<ul>{buttonList}</ul>
</header>
</div>
);
}
Could anyone explain this behaviour to me and/or offer a way of doing this which doesn't do this?
Due to context this. It's must be () => {}. Let's use function and useCallback hook instead.

Resources