functions vs class components to handle events and attach arguments - reactjs

I am trying to choose which is the most optimized way to write a React component that has to handle an event and send some data.
Hi I am tinkering and trying to get comfortable writing react components.
This piece of code fetches some array and makes buttons out of it.
https://codepen.io/daifuco/pen/OdxWYZ
App is just the main component, nothing special about it.
My question: As you can see in the working code, Header + GenreButton:
class Header extends Component {
render() {
return (
<div clasName="footer">
{this.props.data.map((genre)=>
<GenreButton clicky={this.props.clickytoTop}genre={genre}/>
)}
</div>
)
}
}
class GenreButton extends Component {
handleClick = () => {
this.props.clicky(this.props.genre)
}
render() {
return (
<div className="genreButton" onClick={this.handleClick}>{this.props.genre}</div>
)
}
}
has the same result as Header2, which is just a functional component, but as far as I know , Header2 creates a callback every time it renders each div.
function Header2(props) {
return (
<div clasName="footer">
{props.data.map((genre)=>
<div className="genreButton" onClick={()=>props.clickytoTop(genre)}>
{genre}
</div>)}</div>
)
}
So, I understand that Header2 is not the optimal way to design it?
are Header + GenreButton more optimized? Is there any way to improve it?

If you ever have the choice, it is usually preferable to use a stateless component, as they do not have to manage a state which would slow down your application.
To choose between one or the other, if your component needs to re-render itself, use a class, if not, use a function.
Your GenreButton does not need to be a class component either in this case :
const Header = ({ data, clickytoTop }) => ( //Deconstructs your props
<div clasName="footer">
{data.map((genre) =>
<GenreButton clicky={clickytoTop} genre={genre} />
)}
</div>
)
To avoid creating a new function everytime in your render, you could use a decorated arrow function, that you will bind this way :
const GenreButton = ({ clicky, genre }) => <div className="genreButton" onClick={clicky(genre)}>{genre}</div>
The function passed in the header props will now have the following declaration, to handle both sets of parameters :
clickFunction = genre => ev => {
}
When calling clicky(genre) it will return another function capable of accepting your click event.
<Header clickytoTop={this.clickFunction}/>
Second version (same result) :
const Header2 = ({ data, clickytoTop }) => ( //Deconstructs your props
<div clasName="footer">
{data.map(genre =>
<div className="genreButton" onClick={clickytoTop(genre)}>{genre}</div>
)}
</div>
)

Related

Does it consider bad if a react component without any state is defined and rendered inside another react component?

I know that we shouldn't define and render a react component inside another react component because it's going to mess up the states of those components.
But for the example below, I'm wondering if there are any potential issues.
export default function Post() {
const { number } = useParams();
const headerQuery = useHeader(number);
const bodyQuery = useBody(number);
const Header = () => {
if (headerQuery.isLoading) {
return <p>Loading...</p>;
}
// Assume that headerQuery.data here is just a simple string
return <div>{headerQuery.data}</div>;
};
const Body = () => {
if (bodyQuery.isLoading) {
return <p>Loading...</p>;
}
return <div>{bodyQuery.data}</div>
};
return (
<div>
{Header()}
{Body()}
{/* or
<Header />
<Body />
*/}
</div>
);
}
Because those Header and Body components don't have any states so I think it's the same as below:
export default function Post() {
const { number } = useParams();
const headerQuery = useHeader(number);
const bodyQuery = useBody(number);
const header = headerQuery.isLoading ? <p>Loading...</p> : <div>{headerQuery.data}</div>
const body = bodyQuery.isLoading ? <p>Loading...</p> : <div>{bodyQuery.dat}</div>
return (
<div>
{header}
{body}
</div>
);
}
The reason I want to define header and body as components as in the first code snippet is because I don't want to use more than one ternary operator if I have to handle more than the loading state (isLoading ? isSomething ? <h1>...<h1> : <h2>...<h2> : <div>...<div>).
I can absolutely move header and body out of post but if they're nested inside post, are there any issues?
It's perfectly OK to have react components that contain other react components. The contained components will only re-render if they rely on state that has changed, and even if they do re-render - that just updates he virtual dom which isn't free but is pretty light weight.
Each component can have its own state. That is OK and how things work. What you cannot do is introduce "conditional state" into a component. i.e. all your useXXXXX code should be at the top of your component and not inside a function you call or within an if statement, etc.
You absolutely want to use components, e.g. <Header /> and not {Header()).
I recommend watching this video to learn about the differences between <Component /> and {Component()}.

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

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

passing a function as a prop from a functional component to a class component

I am learning react by coding a gamertag generator. It generates random tags and keeps a list, and each can be rated. My main app is a functional component utilizing the useState hook to set state: an array of objects with details about the tags, namely a star rating system.
I generate each gamertag using a react component Tag, within, it uses a functional component, RenderStars to draw the stars. Each tag stars off with 0 stars, so 5 empty stars, and I want the user to change rating by clicking on however many stars, 1-5. RenderStars will then draw however many empty and filled stars as needed.
I have a function in App, changeStars, that I can't seem to get any of the child components to call successfully. I am passing the function to the child components through props.
I've tried writing changeStars in arrow notation and as a plain function. I've tried it without requiring any parameters. I've tried calling it within Tag just using a button. There's other ways I've messed with it that I can't quite recall, just messing with the syntax and trying other things from stackexchange and articles.
I don't bind the function because it's created in a functional component.
This seems like a super basic task and I can't figure it out. Yes i've read the Docs from react.js
Here is some of the code, I'll try to take out as much as possible:
function App() {
const [tagInventory, setTagInventory] = useState([]);
const latestTag = tagInventory[tagInventory.length - 1] ? tagInventory[tagInventory.length -1] : null;
const handleAdd = (tag) => {
uses hook to add tag to state
}
const makeTag = () => {
creates random tag
}
function changeStars(stars,key) {
console.log(stars, key);
//this will change the star rating of an individual tag
}
return (
<main>
A bunch of amazing interface html
<section className="tagInventory">
{tagInventory.map( (item) =>
<Tag
key={item.timeStamp}
tagItem={item}
/>
) }
</section>
</main>
);
};
class Tag extends React.Component {
render() {
const item = this.props.tagItem;
const stars = this.props.tagItem.stars;
const key = this.props.tagItem.timeStamp;
const tagClass = this.props.newTag ? "tag-item new-item" : "tag-item";
return (
<div className={tagClass}>
code to generate cool tag info
</div>
<RenderStars
stars={stars}
changeStars={changeStars}
newTag={false}
key={key}
/>
</div>
);
}
}
const RenderStars = (props) => {
// ref for using symbol tag https://css-tricks.com/svg-symbol-good-choice-icons/
return (
i load svg of stars then can display as many as i need later...
now i draw 4 stars, for the example i'll stop at the first, here's the call..
{props.stars === 0 &&
<div>
<svg className="empty-star" onClick={() => props.changeStars(4,props.key)}>
<use xlinkHref="#empty-star" />
now the other stars and whatnot
}
Thanks!
So basically you want to pass the function changeStars from App to Tag and then to RenderStars, is that correct?
If so, you're forgetting to pass it from App to Tag
App:
<section className="tagInventory">
{tagInventory.map( (item) =>
<Tag
key={item.timeStamp}
tagItem={item}
/>
) }
</section>
Should be passing the function:
<section className="tagInventory">
{tagInventory.map( (item) =>
<Tag
key={item.timeStamp}
tagItem={item}
changeStars={changeStars}
/>
) }
</section>
And then on Tag:
const changeStars = this.props.changeStars;

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

Resources