Switch between divs when clicking prev/next buttons using React Hooks - reactjs

First of all, I am really new to React, so there is a lot of stuff I have no idea about. But trying to learn I've come across Hooks, which seems really nice for a noob like me. However, my next "project" I am not quite sure about.
Basically I have 4 divs of the same size, but with different content. What I would like to do is to start at the first div (rest not visible), and then in that container I have a "Next" button, if I click that it changes/switches to the second div, which then has a "Prev" and "Next" button, and so on.
I have no idea if that is even possible with Hooks using useState or something like that.
EDIT with example:
So hopefully this illustrates my idea. And this was my initial idea. I think I may be stuck at the return where I am not sure how to actually insert that into the HTML, or what you call it in React-lingo.
const App = ( ) => {
const initialCount = 0
const [count, setCount] = useState(initialCount)
if (count = 0) {
<div className="box">
<p>DIV 1</p>
<div className="btn" onClick={ () => setCount(count + 1) } >Next</div>
</div>
} else if (count = 1) {
<div className="box">
<p>DIV 2</p>
<div className="btn" onClick={ () => setCount(count - 1) } >Prev</div>
<div className="btn" onClick={ () => setCount(count + 1) } >Next</div>
</div>
}
return (
<div>
{one of the if-statement elements}
</div>
);
};
export default memo(App);

In order to understand this you need to understand how react works, in general to draw/insert HTML elements you must either return it from your function component, or use render class method in your Class component, putting HTML somewhere else will not render them to the page
As for how to achieve this, there are many ways, the simplest for me would be define a set of components you want to render in an array then render them according to the current count number
const App = () => {
const [count, setCount] = React.useState(0);
const components = [
<div>1</div>,
<div>2</div>,
<div>3</div>,
<div>4</div>
]
return <div>
{
// render component from our components array
components[count]
}
{/* show previous button if we are not on first element */}
{count > 0 && <button onClick={() => setCount(count - 1)}>prev</button>}
{/* hide next button if we are at the last element */}
{count < components.length - 1 && <button onClick={() => setCount(count + 1)}>next</button>}
</div>
}

Related

reactstrap, callback hook used as ref weird behaviour

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?

How to avoid irresponsible app by rendering time consuming component later on

I have a component that takes some time to be loaded (1-2 seconds), and when I switch between tabs, it takes a lot of time for the tab to be seen because it waits for that time consuming component to be fully loaded.
Here's an example:
function App() {
const [tab, setTab] = useState(1);
return (
<div>
<button onClick={() => setTab(1)}>tab 1</button>
<button onClick={() => setTab(2)}>tab 2</button>
{tab === 1 && <div>Hello World</div>}
{tab === 2 && (
<div>
<FastRenderComponent />
<FastRenderComponent />
<SlowRenderComponent />
</div>
)}
</div>
)
}
What I'd like to achieve is to be able to render the SlowRenderComponent only after the other components have been rendered so it won't take 1-2 seconds to be able to switch between tabs, but that it would be seamlessly.
How can I achieve that?
Note: I tried to wrap SlowRenderComponent with Suspense but it only worked once. An example to SlowRenderComponent:
export default function SlowRenderComponent() {
let calculation = 0;
for (let i = 0; i < 2 * 1e8; i++) {
calculation += i + Math.ceil(Math.random() * 100);
}
return <div>Slow Component, Calculation={calculation}</div>;
}

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.

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.

How do I idiomatically turn a text component into an input component on click?

I am creating a todo app in React that is basically just a list of items, grouped by category. I want to add functionality such that when I click a single to do(which is a paragraph), it brings up an input with the current text that I can edit and save. How can I do that without manually editing the DOM?
Code:
A single todo item:
import React from 'react';
const Item = props => {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
};
export default Item;
I want to change the toggle to be a radio button and onClick to edit the todo.
sample image of todos
First of all, you will need a prop that updates item.name (this will be needed when you will edit the input)
You didn't explained well how you want it to work, so I made an example where you click on the text to edit it to a text input and also have a button to save the edit.
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
return isEditing ? (
<div>
<input
value={props.item.name}
onChange={e => props.setItemName(e.target.value)}
/>
<button onClick={() => setIsEditing(false)}>Stop Editing</button>
</div>
) : (
<div
className={`item${props.item.purchased ? " purchased" : ""}`}
onClick={() => setIsEditing(true)}
>
<p>{props.item.name}</p>
</div>
);
};
I also created a working codesanbox with the behavior you want.
You will have to maintain a state to change between the TODO Item & TODO Input. Since you are using functional component, you can use useState hook from react to maintain the state as shown below
import React, { useState } from 'react';
const Item = props => {
const [isEditing, setIsEditing] = useState(false);
if (isEditing) {
return (
<div className={`item${props.item.purchased ? ' purchased' : ''}`}>
<input type="text/radio" value={props.item.name}>
</div>
);
} else {
return (
<div
className={`item${props.item.purchased ? ' purchased' : ''}`}
onClick={() => props.toggleItem(props.item.id)}
>
<p>{props.item.name}</p>
</div>
);
}
};
export default Item;
You might need to change the above a bit based on your application structure but this is what you need to follow.

Resources