React UseRef is undefined using a wrapper - reactjs

I'm following this example in order to achive dynamically-created elements that can be printed using react To Print.
I have the following code (showing sections to keep this question as clean as possible):
/*This section is loaded after a user has been selected from a select input*/
{rows?.map((row,index) => (
<PrintHojaVacaciones key={index} vacacion={row}/>
))}
const PrintHojaVacaciones = ({vacacion}) => {
const componentRef = useRef();
return (
<div>
{vacacion.id}
<ReactToPrint
trigger={() => {
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}}
content={() => componentRef.current}
/>
<Diseno_PrintHojaVacaciones ref={componentRef} value={vacacion}/>
</div>
)
}
export default PrintHojaVacaciones
const Diseno_PrintHojaVacaciones = React.forwardRef((props,ref) => {
const { value } = props;
return (
<div ref={ref}>{value.aprobadapor}</div>
);
});
export default Diseno_PrintHojaVacaciones;
Thing is, useRef is undefined. I have been trying with CreateRef as well, with the same result. I also tried to "move" my code to the codesandbox above displayed and it works well, but in my own application, it returns undefined. The whole useRef is new to me and I don't understand it well yet, so any help would be appreciated.
The route is being called using Lazy loading from react router (I don't know if this could be the culprit)

I don't know exactly what I did to make it work, I really have no idea, but my Trigger now looks like this and it is now working (not sure if I'm missing something else). The difference is that the function is not inside brackets after the =>.
trigger={() =>
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}

Related

Single responsibility in React component

I was learning Single responsibility principle in React and created such component:
import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";
export const SingleResponsibilityPrinciple = () => {
const {filteredUsers , isLoading} = useGetRemoteData()
const showDetails = (userId) => {
const user = filteredUsers.find(user => user.id===userId);
alert(user.contact)
}
return <>
<div> Users List</div>
<div> Loading state: {isLoading? 'Loading': 'Success'}</div>
{filteredUsers.map(user => {
return <div key={user.id} onClick={() => showDetails(user.id)}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
</>
}
As you can see above, I have this code
const user = filteredUsers.find(user => user.id===userId);
The question is Is it best practice that if whenever we use map, reduce or any array function in React component, we should extract that logic out of a component, that is, filteredUsers.find(user => user.id===userId); should be extracted and we need to create utility function. So, function should not care about how a particular thing is done. Is it true?
Thank you for your question. I want to advice as follows
It is better for your to check if filteredUsers exists or not in your return.
{filteredUsers?.map(user=>{
//your code
})
You don't need to get specific user as you already loop in your map method. Just simply do something like this
{filteredUsers.map(user => {
return <div key={user.id} onClick={() => showDetails(alert(user.contact))}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
Remember the difference between find, filter method of Javascript array. If you have unique value according to userId, you simply use find method to get the first value not array, if you use filter, you get arrays of the condition. Are you sure you don't need to alert(user[0].contact), not alert(user.contact)?

useEffect not applying classNames

I'm currently working on a project using Next JS, where I seem to have encountered an issue with React. Here is the simplified version of my code. I did try to replicate the issue in codesandbox but I couldn't. I'll keep trying though and if I can, I'll update this post with the link.
const Nav = React.forwardRef<
HTMLDivElement,
{ className?: string; disableAnimation?: boolean }
>((props, ref) => {
const navWrapperRef = useRef<HTMLDivElement>(null);
const navItemsRef = useRef<HTMLSpanElement[]>([]);
useEffect(() => {
const path = window.location.pathname;
if (path === "/") navItemsRef.current[0].classList.add("nav-active");
else if (path.includes("/packages"))
navItemsRef.current[1].classList.add("nav-active");
else if (path.includes("/bhutan"))
navItemsRef.current[2].classList.add("nav-active");
}, []);
return (
<nav className={props.className || "nav"} ref={navWrapperRef}>
<div className="nav-container">
<ul className="nav-ul">
{navLinks.map((link) => (
<button
key={uniqueId(`nav-links-${new Date().getUTCDate()}`)}
data-name={link.name}
className="nav-links"
>
<span
ref={(el) => navItemsRef.current.push(el as HTMLSpanElement)}
className="nav-span"
>
<Link href={link.href}>{link.name}</Link>
</span>
</button>
))}
</ul>
</div>
</nav>
);
});
My objective here is to implement a navigation component without the use of states. I'd like to render out the current active navigation link on the initial page load using the empty dependency array as for the useEffect hook. But I can't seem to get it to work.
My desired output is the following on page load:
The output I get:
However, if I remove the dependency array altogether then all seems to work fine as expected. But if I am not wrong I think this causes performance issues as it re-renders each and every time if there are any other state changes. Any help would be greatly appreciated!
The contents of your useEffect hook will run once on mount and whenever its dependencies change.
As this is reliant on what you have defined as path, I'd move it out of the useEffect and add it as a dependency.
Update: you will have to use next/router's useRouter hook instead of the window directly when working with next.
Demo here.
const { asPath } = useRouter();
useEffect(() => {
if (asPath === "/") navItemsRef.current[0].classList.add("nav-active");
else if (asPath.includes("/packages"))
navItemsRef.current[1].classList.add("nav-active");
else if (asPath.includes("/bhutan"))
navItemsRef.current[2].classList.add("nav-active");
}, [asPath]);

React hooks - Dispatching a reset state from parent component

This is my application with the scenario reproduced, here the demo in codesandbox
I have two components, Leagues ( parent ) and Details ( Child ).
I have a implemented reset button example in the Details Component button which does
const cleanArray = () => {
setDataHomeTeam([]);
};
<button onClick={cleanValue} type="button">Reset</button>
You can see in the demo that is emptying out an array of a team stat
My question is, can i implement the same button but out from Details component and from the parent component Leagues for example? Whats the way to achieve it?
I thought to go this way but i can not get it done.
So in my Details.js
let Details = forwardRef(({ ....
const cleanArray = () => {
setDataHomeTeam([]);
};
useImperativeHandle(ref, () => {
return {
cleanValue: cleanValue
}
});
in App.js
<Leagues/>
<button onClick={cleanValue} type="button">Reset</button>
<Details ref={ref} />
I get this error : 'cleanValue' is not defined no-undef
is it something that i can not do with react? How can i achieve it?
I think your approach sounds correct except for lacking the way of calling the api cleanValue you exposed. Basically you have to call it via a ref you pass to it as following:
function App() {
const ref = useRef();
return (
<>
<Leagues />
{/* call it via ref */}
<button onClick={() => ref.current.cleanValue()} type="button">Reset</button>
<Details ref={ref} />
</>
)
}
Codesandbox link: https://codesandbox.io/s/nifty-raman-c0zff?file=/src/components/Details.js
I don't completely understand what you are trying to do, but here is a solution I think is going to work for your problem
let's say you wanna filter that array with the selected team which is liverpool, first if you have control over the incoming data I recommend changing the obj in the array likethis
{day : 16 , teamName:"liverpool"}, this is going to help you filter that array later,
then you useEffect & useState to update that array
[teams, setTeams] = useState([]);
// the array changes here {day: 1 , teamName : "sao paulo"} , {day:2 ,teamname:"liverpool"}]
useEffect(()=>{
setTeams((T)=>{
return T.filter((oneTeam)=>{
return oneTeam.teamName == selectedTeam;
});
})
},[teams,selectedTeam]);

What is the right place to call a function before render in React?

I have some issue on understandsing the lifecycle in React, so iam using useEffects() since i do understand that it was the right way to call a method before the component rendered (the replacement for componentDidMount ).
useEffect(() => {
tagSplit = tagArr.split(',');
});
And then i call tagSplit.map() function on the component, but it says that tagSplit.map is not a function
{tagSplit.map((item, index) => (
<div className="styles" key={index}>
{item}
</div>
))}
Is there something wrong that i need to fix or was it normal ?
useEffect runs AFTER a render and then subsequently as the dependencies change.
So yes, if you have tagSplit as something that doesn't support a map function initially, it'll give you an error from the first render.
If you want to control the number of times it runs, you should provide a dependency array.
From the docs,
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
This article from Dan Abramov's blog should also help understand useEffect better
const React, { useState, useEffect } from 'react'
export default () => {
const [someState, setSomeState] = useState('')
// this will get reassigned on every render
let tagSplit = ''
useEffect(() => {
// no dependencies array,
// Runs AFTER EVERY render
tagSplit = tagArr.split(',');
})
useEffect(() => {
// empty dependencies array
// RUNS ONLY ONCE AFTER first render
}, [])
useEffect(() => {
// with non-empty dependency array
// RUNS on first render
// AND AFTER every render when `someState` changes
}, [someState])
return (
// Suggestion: add conditions or optional chaining
{tagSplit && tagSplit.map
? tagSplit.map((item, index) => (
<div className='styles' key={index}>
{item}
</div>
))
: null}
)
}
you can do something like this .
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
let tagSplit = tagArr.split(',');
setArr(tagSplit);
}, []);
return (
<>
{arr.map((item, index) => (
<div className="styles" key={index}>
{item}
</div>
))}
</>
)
}
Answering the question's title:
useEffect runs after the first render.
useMemo runs before the first render.
If you want to run some code once, you can put it inside useMemo:
const {useMemo, Fragment} = React
const getItemsFromString = items => items.split(',');
const Tags = ({items}) => {
console.log('rendered')
const itemsArr = useMemo(() => getItemsFromString(items), [items])
return itemsArr.map((item, index) => <mark style={{margin: '3px'}} key={index}>{item}</mark>)
}
// Render
ReactDOM.render(<Tags items='foo, bar, baz'/>, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
For your specific component, it's obvious there is no dilema at all, as you can directly split the string within the returned JSX:
return tagArr.split(',').map((item, index) =>
<div className="styles" key={index}>
{item}
</div>
)
But for more complex, performance-heavy transformations, it is best to run them only when needed, and use a cached result by utilizing useMemo

How To do mapping of click , text and className in Redux's presentational componant

I am new to react-redux. I am trying to do mapping in Redux presentational component. However, I am failing to do so. My code is as following:
const ABC = ({isAOn, isBOn, isCOn, isDOn,onAClick, onBClick, onCClick, onDClick }) => {
const Array = [{click:'onAClick',style:'isAOn',text:'AAAA'},
{click:'onBClick',style:'isBOn',text:'BBBB'},
{click:'onCClick',style:'isCOn',text:'CCCC'},
{click:'onDClick',style:'isDOn',text:'DDDD'}]
return (
<div>
{Array.map((test) =>
<div onClick={() => test.click} className={({test.style})?'DIV-ON':'DIV-OFF'}>{test.text}</div>
)}
</div>
)
}
export default ABC
Note: 1) isAOn, isBOn are boolean, which are used to toggle className of component.
2) I have also tried writing onClick differently. For example, onClick = {test.click} etc.
3) I have run code without mapping, it works fine. However, it is creating very large amount of repetitive coding which I want to reduce using mapping.
4) It will be very helpful, if you provide solution by running above code in fiddle.
You want something like this:
import React from "react";
import ReactDOM from "react-dom";
const onAClick = () => {
alert("clicked");
};
const App = ({ isAOn, onAClick }) => {
const Array = [{ click: onAClick, style: "isAOn", text: "AAAA" }];
return (
<div>
{Array.map(test => (
<div
onClick={() => test.click()}
className={isAOn ? "DIV-ON" : "DIV-OFF"}
>
{test.text}
</div>
))}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App onAClick={onAClick} />, rootElement);
Working example here.
A couple issues,
test.click is not a function. It is actually just a string.
Even if it was, you are not invoking the function. To do that you need to have () at the end of the call.
you have type.isAOn and according to what you posted type isnt defined anywhere
Another approach could be as follows.
Instead of deconstructing your props, you should keep it together. This will allow you use the string name in the array to find it within the props object and directly pass it into the onClick prop. This removes the need for placing an anonymous function in to call the function.
const ABC = props => {
const Array = [{click:'onAClick',style:'isAOn',text:'AAAA'},
{click:'onBClick',style:'isBOn',text:'BBBB'},
{click:'onCClick',style:'isCOn',text:'CCCC'},
{click:'onDClick',style:'isDOn',text:'DDDD'}]
return (
<div>
{Array.map((test) =>
<div onClick={props[test.click]} className={({type.isAOn})?'DIV-ON':'DIV-OFF'}>{test.text}</div>
)}
</div>
)
}
export default ABC
I didnt make any assumptions about the type.isAOn so I left it how it was but you can follow the same pattern that was done for the onClick to gain access to the props you are passing down.

Resources