React passing props (react-to-print) - reactjs

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

Related

Why is my JSX.Element not updating when the state is updated unless I wrap it in another JSX.Element?

I am working on a typescript react project, both of which are new to me. I have the following model viewer and the type below it that consumes it. I deleted some of the bottom type so it is easier to read, but basically, getData gets called in useEffect. When the data is obtained, getData calls selectModule which, among other things, gets images from the data and sets shownImages. This all works as expected and shownImages is being set with the data expected. But when viewer 3d gets created, shownImages is not yet set. And it does not get recreated when shown images is set. However, if I replace <Viewer3D items={shownImages} /> with <Review3dComponent></Review3dComponent>, it does get recreated when shownImages is set. And I would like to know why Review3dComponent gets updated but Viewer3D does not.
interface Viewer3DProps {
items: Image[] | Image;
}
const Viewer3D = ({ items }: Viewer3DProps) => {
return (
<div className="threed-viewer">
<OBJModel
items={items}
enableTransform={true}
position={{ x: 0, y: 0, z: 0 }}
texPath=""
style={{ height: `100% !important`, width: `100% !important` }}
/>
</div>
);
};
export default Viewer3D;
export const File3dReview: NextPage = (): ReactElement => {
{
const [shownImages, setShownImages] = useState<Image[]>([]);
const selectModule = (module: IModule) => {
setCurrentModule(module);
setCurrentGroups(module.group);
const images: Image[] = module.group.flatMap(
(group) => group.images,
) as unknown as Image[];
setShownImages(images);
console.log(`set images`, images);
};
const getData = useCallback(async () => {
//gets data and calls select module
}, []);
useEffect(() => {
getData();
}, [getData]);
const Review3dComponent = () => {
return (
<div>
<Viewer3D items={shownImages} />
</div>
);
};
return (
<div>
<div>
<header className="app-header">
<div className="app-navigation">
<Space size="middle">
<div className="ic-24 color-secondary">
<FiArrowLeft
onClick={() => {
router.back();
}}
/>
</div>
<div>{name}</div>
</Space>
<nav>
<Button shape="circle" onClick={toggleDrawer}>
<MenuOutlined />
</Button>
</nav>
</div>
</header>
</div>
<Viewer3D items={shownImages} />
{/* <Review3dComponent></Review3dComponent> */}
<DrawerComponent></DrawerComponent>
</div>
);
}
};
export default File3dReview;

Trigger method on parent component from child component - react

I have a swipeable component with an open and close function that I would like to trigger from a child of the component. I don't think using state to do it in this instance would be correct since I want to avoid a re-render while the swipeable component is animating (please correct me if I'm wrong).
I'm using react-native-gesture-handler/swipeable and following their example here
SwipeCard component
import React, { useRef } from 'react';
import { RectButton } from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import Animated from 'react-native-reanimated';
const AnimatedView = Animated.createAnimatedComponent(View);
export const SwipeCard = ({ children }) => {
let swipeableRow = useRef(null);
let renderRightActions = (_progress, dragX) => {
let scale = dragX.interpolate({
inputRange: [-80, 0],
outputRange: [1, 0],
extrapolate: 'clamp',
});
return (
<RectButton style={styles.rightAction} onPress={close}>
{/* Change it to some icons */}
<AnimatedView style={[styles.actionIcon]} />
</RectButton>
);
};
let close = () => {
swipeableRow?.close();
};
let open = () => {
swipeableRow?.openRight();
};
return (
<Swipeable
ref={swipeableRow}
renderRightActions={renderRightActions}
friction={2}
rightThreshold={40}
>
{children}
</Swipeable>
);
};
Below is the component where I'm using SwipeCard and the Toggle is the event I want to use to fire the open() method in the SwipeCard component.
<Row>
{arr.map((item) => {
return (
<SwipeCard key={item.id}>
<CardContainer>
<CleaningCard
cleaningData={item}
/>
<Toggle onPress={() => {}}>
<Icon name="dots" />
</Toggle>
</CardContainer>
</SwipeCard>
);
})}
</Row>
You can use the render prop pattern and pass close, open as arguments.
Parent component where SwipeCard is used:
<Row>
{arr.map((item) => {
return (
<SwipeCard key={item.id}>
{({ close, open }) => (
<CardContainer>
<CleaningCard cleaningData={item} />
<Toggle
onPress={() => {
// close()/open()
}}
>
<Icon name="dots" />
</Toggle>
</CardContainer>
)}
</SwipeCard>
);
})}
</Row>;
SwipeCard component:
<Swipeable
ref={swipeableRow}
renderRightActions={renderRightActions}
friction={2}
rightThreshold={40}
>
{children({close, open})}
</Swipeable>
We're simply making the children a function that takes an object and returns the JSX. The required object is passed as an argument (children({close, open})).

React: cannot access div element's value

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

How to use a useState function in another component with props?

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

intercommunication between pair of elements created in array of React.createElement

Let's say I have 2 react elements: componentSender and componentReceiver. That need to be generated in a loop N times.
The special thing they have is that every time someone click in one componentSender, a prop will change in the respective componentReceiver.
This pair of components could be as simple as:
function ComponentReceiver(props) {
return (
<div>{`Listening to "Sender ${props.indexVar}" and it last received: ${props.showVar}`}</div>
);
}
function ComponentSender(props) {
return (
<input type="button" onClick={() => {props.onChangeValue(props.indexVar);}}
value={`SENDER for ${props.indexVar}> `}
/>
);
}
I am using React.createElement in a loop and creating the pairs, you can see it here:
https://codepen.io/danieljaguiar/pen/bGVJbGw?editors=1111
The big problem in my demo is that, when I change the state in the parent (APP), the child components don't re-render.
You have to fix up few things:
try not to store jsx in state. Iterate and render directly in render.
in handleChangeValue function, the show state reference is not changed at all and hence the component is not re-rendered. Make sure to take a copy of show (use spread operator) and then update state.
remove unnecessary code in useEffect and
Working & simplified copy of your code is here in the codesandbox
Code Snippet with fixes
function ComponentReceiver(props) {
return (
<div>{`Listening to "Sender ${props.indexVar}" and I received: ${
props.showVar
}`}</div>
);
}
function ComponentSender(props) {
return (
<input
type="button"
onClick={() => {
props.onChangeValue(props.indexVar);
}}
value={`SENDER for ${props.indexVar} ----------------------> `}
/>
);
}
export default function App() {
const [show, SetShow] = React.useState([]);
const [pageElements, setPageElements] = React.useState([]);
const handleChangeValue = val => {
const updatedShow = [...show];
updatedShow[val] = !updatedShow[val];
SetShow(updatedShow);
};
React.useEffect(() => {
let index = 0;
let elements = [];
while (index < 5) {
show[index] = true;
SetShow([...show]);
elements.push(
<div key={index} style={{ display: "flex", margin: "20px" }}>
<ComponentSender
key={index + "s"}
indexVar={index}
onChangeValue={handleChangeValue}
/>
<ComponentReceiver
key={index + "R"}
indexVar={index}
showVar={show[index]}
/>
</div>
);
index++;
SetShow([...show]);
}
setPageElements(elements);
}, []);
return (
<div>
{[...Array(5).keys()].map((_, index) => {
return (
<div key={index} style={{ display: "flex", margin: "20px" }}>
<ComponentSender
key={index + "s"}
indexVar={index}
onChangeValue={handleChangeValue}
/>
<ComponentReceiver
key={index + "R"}
indexVar={index}
showVar={show[index]}
/>
</div>
);
})}
</div>
);
}

Resources