Single responsibility in React component - reactjs

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

Related

React.js warning when iterating a list with map()

I got this warning from web debug console:
react-jsx-dev-runtime.development.js:87 Warning: Each child in a list should have a unique "key" prop.
Check the render method of App. See https://reactjs.org/link/warning-keys for more information. at div at App (http://localhost:3000/static/js/bundle.js:31:80)
Below is my code:
import './App.css';
import {ethers} from "ethers";
import { useState } from 'react';
function App() {
const [account, setAccount] = useState("")
const [data, setData] = useState([])
console.log(data);
const connect = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum)
let res = await provider.send("eth_requestAccounts", []);
setAccount(res[0]);
getData(res[0]);
}
const getData = (_account) => {
const options = {method: 'GET', headers: {accept: 'application/json'}};
fetch(
'https://api.opensea.io/api/v1/collections?asset_owner=0x3FB65FEEAB83bf60B0D1FfBC4217d2D97a35C8D4&offset=0&limit=3',
// `https://api.opensea.io/api/v1/collections?asset_owner=${_account}&offset=0&limit=3`,
options)
.then(response => response.json())
.then(response => {
setData(response);
console.log(response)})
.catch(err => console.error(err));
};
return (
<div className="App">
<button
onClick={connect}>
Connect
</button>
{data.map(nft => {
return(<div>
<img src={nft.featured_image_url} width="100px" height="100px"/>
<p>
{nft.discord_url}
</p>
<p>
{nft.banner_image_url}
</p>
</div>)
})}
<button
onClick={getData}>
GetData
</button>
</div>
);
}
export default App;
The code actually works as I expected. but when opening debug console from chrome I can see this warning pasted above.
Not sure why this warning? need some help, thank you
Googled this warning msg but cannot find useful info for this warning.
Is this warning a real issue or this can be ignored?
You need to add a key to your returned element, because React need to differentiate each elements.
You just need to add the parameter key to your block like:
{data.map(nft => (
<div key={nft.id}>
<img src={nft.featured_image_url} width="100px" height="100px"/>
<p>
{nft.discord_url}
</p>
<p>
{nft.banner_image_url}
</p>
</div>
))}
Why did I used nft.id ?
Most often, peoples use array map()'s index property as key, but it can be a bad practice depending on your goal.
using:
{data.map((nft, index) => (
<div key={index}>
...
))}
Works pretty fine, BUT in some cases (not that rare), when you perform edit action on your array's element, and you end up sorting them, React will be very confused.
Imagine you create an online music platform such as Spotify, and your API return you a list of your own playlist, ordered by name. The day you'll edit one playlist name, your entire playlist will have unwanted comportement because your element array's order will be modified, and the index you used as key.
So you may use map's index property as key, but be aware of what you need, it's generally better to use your element's id, uuid or other unique value as key.
See more on the official website
You must provide a unique key when you map with react.
Here is how :
{data.map((nft, index) => {
return(<div key={index}>
This is just an example. You can provide your own index.div key={nft} could work too if it is unique for each data.

React UseRef is undefined using a wrapper

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

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

useRef in a dynamic context, where the amount of refs is not constant but based on a property

In my application I have a list of "chips" (per material-ui), and on clicking the delete button a delete action should be taken. The action needs to be given a reference to the chip not the button.
A naive (and wrong) implementation would look like:
function MemberList(props) {
const {userList} = this.props;
refs = {}
for (const usr.id of userList) {
refs[usr.id] = React.useRef();
}
return <>
<div >
{
userList.map(usr => {
return <UserThumbView
ref={refs[usr.id]}
key={usr.id}
user={usr}
handleDelete={(e) => {
onRemove(usr, refs[usr.id])
}}
/>
}) :
}
</div>
</>
}
However as said this is wrong, since react expects all hooks to always in the same order, and (hence) always be of the same amount. (above would actually work, until we add a state/any other hook below the for loop).
How would this be solved? Or is this the limit of functional components?
Refs are just a way to save a reference between renders. Just remember to check if it is defined before you use it. See the example code below.
function MemberList(props) {
const refs = React.useRef({});
return (
<div>
{props.userList.map(user => (
<UserThumbView
handleDelete={(e) => onRemove(user, refs[user.id])}
ref={el => refs.current[user.id] = el}
key={user.id}
user={user}
/>
})}
</div>
)
}

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