When I press on the that element I want to log it's attributes(value). I think, I am not using useRef hook correctly.
Link:
https://codesandbox.io/s/access-dom-element-forked-lphg6?from-embed=&file=/src/App.js:0-405
import "./styles.css";
import { useRef } from "react";
export default function AccessingElement() {
const elementRef = useRef();
const fake_data = ["hello", "bye", "yes", "no"];
return (
<div>
{fake_data.map((item, idx) => (
<div value={item} ref={elementRef} key={idx} onClick={() => console.log(elementRef.current)}>
{item}
</div>
))}
</div>
);
}
The issue is being seen because all the items are being assigned to the same ref. Hence, on clicking any element, text corresponding to the last assigned item (i.e no) gets logged.
In this case, an array of refs needs to be maintained, such that each ref in the array corresponds to an item. Something like this :
import "./styles.css";
import { useRef } from "react";
export default function AccessingElement() {
const elementRef = useRef([]);
const fake_data = ["hello", "bye", "yes", "no"];
return (
<div>
{fake_data.map((item, idx) => (
<div
ref={el => elementRef.current[idx] = el}
key={idx}
onClick={() => console.log(elementRef.current[idx])}>
{item}
</div>
))}
</div>
);
}
Here is the working CodeSandbox Link
You can not set value for the div. If you want to set some data for the div, you can do like this.
<div data-val={item} ref={elementRef} key={idx}
onClick={() => console.log(elementRef.current.dataset.val)}>
{item}
</div>
If you want to get the text inside the div, you just need to use textContent
elementRef.current.textContent
Related
I'm a newbie in react. I have two class in css file. One is btn and another is btn-active. I want to set an btn-active class to the first button by default and when I click on other buttons it'll be remove and add to the current button. I'll be thankful if anyone help me about this. Thanks in advance.
import { useState } from "react";
import buttons from "../data/buttons.json";
const Home = () => {
return (
<div>
{buttons.map((item, index) => {
return (
<button key={index} className="btn">
{item.valu}
</button>
);
})}
</div>
);
};
Keep a state to handle the selected button index.
Try like below:
import { useState } from "react";
const Home = () => {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div>
{buttons.map((item, index) => {
return (
<button
key={index}
onClick={() => setSelectedIndex(index)}
className={`btn ${selectedIndex === index ? "active" : ""}`}
>
{item.valu}
</button>
);
})}
</div>
);
};
Hi I have been using this package react-to-print to print document and it works really well. Passing value to child component works and I can print the dynamic data too. However, I am facing problem to pass dynamic data of array list. It always gets the last item of array. I wrote an example, please take a look at it
import * as React from "react";
import { useRef } from "react";
import ReactToPrint from "react-to-print";
const ComponentToPrint = React.forwardRef((props, ref) => {
const { value } = props;
return (
<div className="print-source" ref={ref}>
Number {value}
</div>
);
});
export default function App() {
const componentRef = useRef();
const numbers = [1, 2, 3, 4, 5];
return (
<>
{numbers.map(function (item, index) {
return (
<div style={{ display: "flex" }}>
<li key={index}>{item}</li>
<ReactToPrint
trigger={() => <button type="primary">Print</button>}
content={() => componentRef.current}
/>
<ComponentToPrint ref={componentRef} value={item} />
</div>
);
})}
</>
);
}
Live Demo
Whenever I click the print button, I expect to send the unique value of number to child component but every time I am getting the last value of array. What am I doing wrong?
Because there's just one componentRef instance, which on the order of rendering will have the last rendered value.
Instead each returned component from App needs to have its own instance of componentRef.
This can be achieved if you
make the returned html from App a component too (say ComponentToPrintWrapper)
have this component its own componentRef.
const ComponentToPrintWrapper = ({ item }) => { // 1.
const componentRef = useRef(); // 2.
return (
<div style={{ display: "flex" }}>
<li>{item}</li>
<ReactToPrint
trigger={() => <button type="primary">Print</button>}
content={() => componentRef.current}
/>
<ComponentToPrint ref={componentRef} value={item} />
</div>
);
};
Use ComponentToPrintWrapper on your App instead
...
export default function App() {
const numbers = [1, 2, 3, 4, 5];
return (
<>
{numbers.map(function (item, index) {
return <ComponentToPrintWrapper key={index} item={item} />;
})}
</>
);
...
}
This will ensure each return element has its own componentRef instead.
CodeSandbox
Just wanted to implement toggling faq type style. I have tried couple different ways but I can't seem to get it right. Basically i want to be able to expand the paragraph when the button is click. The paragraphs should expand one at a time. The code below is able to expand and close the paragraph one at a time as well but if you click on different button and there is an open paragraph, it just closes the first paragraph instead of opening the other paragraph.
Here is a link to sandbox as well: https://codesandbox.io/s/pensive-lichterman-nmjpy?file=/src/App.js:0-886
import { useState } from "react";
import "./styles.css";
import { info } from "./data";
export default function App() {
const [itemId, setItemId] = useState(null);
const [expand, setExpand] = useState(false);
const handleClick = (id) => {
setItemId(id);
setExpand((preState) => !preState);
};
return (
<div className="details">
{info.map(({ title, details, id }, i) => {
return (
<div key={id} className="details-wrapper">
<div>
<h3 className="title">{title}</h3>
<button onClick={() => handleClick(id)}>+</button>
</div>
<p
className="text"
style={{
display: itemId === id && expand ? "block" : "none"
}}
>
{details}
</p>
</div>
);
})}
</div>
);
}
You are only keeping a single id and expand state. So no matter what you do, when the page is re-renedered onClick it sees a single id and only sets the display style for that id.
If you wish to control each element separately then they each need their own expand state saved. There are several ways to do this, but it's probably best to create an "Expandable" component that saves its own state. Something like this:
https://codesandbox.io/s/polished-rain-xnez0?file=/src/App.js
Pretty much, I create a state for each paragraph. That’s what you need to create individual components with their own state.
export default function App() {
return (
<div className="details">
{info.map(({ title, details, id }, i) => {
return <ItemParagragh key={id} title={title} details={details} />;
})}
</div>
);
}
const ItemParagragh = ({ title, details }) => {
const [expand, setExpand] = useState(false);
const handleClick = () => {
setExpand((preState) => !preState);
};
return (
<div className="details-wrapper">
<div>
<h3 className="title">{title}</h3>
<button onClick={() => handleClick()}>+</button>
</div>
<p
className="text"
style={{
display: expand ? "block" : "none"
}}
>
{details}
</p>
</div>
);
};
I write a React.js note web application where a user can add up to 10 notes.
I use map() to iterate the array of notes, and a useState(1) hook to update its count (the default number of notes is 1), so I would like to do something like this:
{[...Array(noteCount)].map((_, i) => <Note onUpdateNoteCount={() =>setNoteCount(n => n - 1)} key={i} />)}
The thing is that the Note() component is inside a Main() component which is in the App() component, so I want to get the needed values as props of App(), and than use them in Note(), but can not figure out how and where to put it.
Thanks!
App.js
import React from 'react';
import Header from './Header';
import Main from './Main';
function App () {
const [noteCount, setNoteCount] = React.useState(1);
function multiplyNoteComponent () {
if (noteCount < 20) {
setNoteCount(n => n + 1)
}
else {
alert('too many notes. remove or combine some of them together!')
}
}
return (
<div>
<Header/>
{[...Array(noteCount)].map((_, i) => <Main onUpdateNoteCount={() =>setNoteCount(n => n - 1)} key={i} />)}
<button
style={{left: '5%'}}
id='addNoteBtn'
onClick={multiplyNoteComponent}
title='Add a note'
>
+
</button>
</div>
);
}
export default App;
Main.js
import React from 'react';
import Note from './Note';
function Main () {
return (
<main>
your notes are:
<Note/>
</main>
)
}
export default Main;
Note.js
import React from 'react';
function Note () {
return (
<div> <button title='delete note' onClick={}>X</delete>
<li>
<input type='text'/>
</li>
</div>
)
}
export default Note
Edit: the reason I think I need the setNoteCount() function to be used in the Note() component, is for the count down when a note is being deleted (every note has its own delete button).
I would recommend this architecture of the your App.
Store the Notes array at the App level.
Add a note using NoteInput which adds a notes to your Notes array.
Map your Notes using the Note component which takes onDelete as a prop from App level.
Your App component should be responsible for storing and delete a note from the state.
In your example, notesCount is meant to a derivative state.
i.e it could be derived simply from the Notes array (notes.length).
So, rather than storing notesCount, I recommend storing notes and deriving count from it.
You could see the working example here :- https://stackblitz.com/edit/react-g19tei
import React from "react";
import "./style.css";
const NOTES_ALLOWED = 10;
export default function App() {
const [notes, setNotes] = React.useState([]);
function addNote(newNote) {
if (notes.length === NOTES_ALLOWED) {
alert(`Only ${NOTES_ALLOWED} notes are allowed to be added`)
} else {
setNotes([...notes, newNote]);
}
}
function handleDelete(deleteNoteIdx) {
const newNotes = [...notes];
// delete the note at the specific index
newNotes.splice(deleteNoteIdx, 1)
setNotes(newNotes);
}
return (
<div>
<div style={{ marginTop: 20, marginBottom: 20 }}>
<p>Your notes are</p>
{notes.map((note, idx) => (
<Note
note={note}
onDelete={() => handleDelete(idx)}
/>
))}
</div>
<NoteInput onAdd={addNote} />
</div>
);
}
function Note({ note, onDelete }) {
return (
<div>
<p>{note}
<button onClick={onDelete}>Delete Note</button>
</p>
</div>
)
}
function NoteInput({ onAdd }) {
const [note, setNote] = React.useState('');
function handleSubmit(e) {
e.preventDefault();
const noteToBeSend = note;
setNote('')
onAdd(noteToBeSend.trim());
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={note}
onChange={e => setNote(e.target.value)}
required
/>
<button type="submit">Add Note</button>
</form>
</div>
)
}
I was wondering if there is a way to make it work. You can check the snippet that I added below.
import React, { useState } from "react";
import Item from "./Item";
export default function App() {
const [hoveredItem, setHoveredItem] = useState(null);
const items = [...Array(5000).keys()];
const handleMouseEnter = (item) => {
setHoveredItem(item);
};
return (
<div className="App">
{items.map((item) => (
<Item
key={item}
isHover={hoveredItem === item}
onMouseEnter={() => handleMouseEnter(item)}
>
Item {item}
</Item>
))}
</div>
);
}
The code above is just a snippet, using the pseudo class (:hover) wont work for what I need. The id of the item hovered needs to be in the state.
Here is a codesandbox link if you want to play around: https://codesandbox.io/s/floral-cache-wfxws
I do it using hooks:
const [hover, setHover] = useState(false)
return (
<div>
<div
onMouseOver={()=>setHover(true)}
onMouseOut={()=>setHover(false)}
className={" " + (hover ? "my-css-class" : "") }>
My text
</div>
</div>
)