How to group props and make code more readable? - javascript

Is this the only way of defining properties?
<YAxis
id="axis1"
label="Requests"
style={{ labelColor: scheme.requests }}
labelOffset={-10}
min={0}
max={1000}
format=",.0f"
width="60"
type="linear"
/>
This can be really cluttered if there is a long list of properties
It remembers me a bit of inline css, which can become hard to read.
can't we just say:
var yAxis_props = ( id = ... label = ... )
and then insert them like:
<YAxis yAxis_props />

Yes can do that, define yAxis_props as an object with all the key-values. Like this:
var yAxis_props = {
id: '',
label: ''
}
Then using spread operator pass all the values as a separate prop, like this:
<YAxis
{...yAxis_props}
/>
Check React Doc.
Ok, you are getting confused between these two ways:
// first way
<Component id='', label='' />
// second way
const obj = { id:'', label: ''}
<Component {...obj} />
Behind the scene both are same. Check this Babel repl output of these two ways. We write jsx, which will be converted into React.createElemet(component/html tag, props, children).
Code:
function ComponentA () {
return( <div>Hello</div> )
}
function ComponentB() {
return (
<div>
<A id='a' label='b' />
<A {...{id:'a', label:'b'}} />
</div>
)
}
Converted form:
function ComponentA() {
return react("div", null, "Hello");
}
function ComponentB() {
return react(
"div",
null,
react(A, {
id: "a",
label: "b"
}),
react(A, {
id: "a",
label: "b"
})
);
}

You can achieve something similar using spread operator.
let props={a:1, b:2}
...
<MyComponent {...props}/>

While the answers here cover the spread approach which is valid and works great, I would like remind that having too many props for a single component might indicate that this component suffers from too many variations, which in turn makes it really hard to maintain and extend.
In such cases you would like to consider the approach of Render Props or "children as function" as it is sometimes called.
This approach allows you to keep your component logic and basic behavior in a single place, while the developers using it can use it in composition to alter the way the component renders and interacts with the surroundings.
There is a great talk by Kent C. Dodds about this here.
Hope this helps :)

Related

Should functions within React functional components be wrapped?

I have a functional component that has one function within it, renderMessages.
const MessageContainer = (props) => {
const renderMessages = () => {
return props.messages.map((message, index) => {
return(
<Message
key={index}
username={message.username}
message={message.message}
fromCurrentUser={message.fromCurrentUser}
/>);
})
}
return(
<div className='messages'>
{renderMessages()}
</div>
)
}
However, I realized that instead of wrapping renderMessages function on the map, I can just have:
const renderMessages = props.messages.map((message, index) => {
return(
<Message
key={index}
username={message.username}
message={message.message}
fromCurrentUser={message.fromCurrentUser}
/>);
})
}
And as a result, my final return would just contain
return(
<div className='messages'>
{renderMessages}
</div>
)
In a class-based component and within a render function, I'd use the last of the two. Which of the two is considered the best practice when using functional components, and why?
EDIT:
Which of the two is considered the best practice when using functional components, and why?
Best practices change with context - e.g. the team you're working on - so this is an opinion-based question out of the gate.
That being said, in my opinion, I wouldn't do either. I'd do (and I do) this:
const MessageContainer = (props) => {
return (
<div className='messages'>
{props.messages.map((message, index) => (
<Message
key={index}
username={message.username}
message={message.message}
fromCurrentUser={message.fromCurrentUser}
/>
))}
</div>
)
}
What's the purpose of the extra variable anyway?
While you're at it, don't use indexes for keys
The dirty secret about all those extra methods you stuck on your class components that encapsulated rendering logic is that they were an anti-pattern - those methods were, in fact, components.
EDIT #2
As pointed out in the other answer, the most performant solution for this specific use case is specifying the map function outside the functional component:
const renderMessage = (message,index) => (
<Message
key={index}
{...message}
/>
)
const MessageContainer = (props) => {
return (
<div classname='messages'>
{props.messages.map(renderMessage)}
</div>
);
}
But, you shouldn't prematurely optimize and I would advocate for the original solution I posted purely for simplicity/readability (but, to each their own).
Good job separating the mapping outside the component's return, because this way you'll be only calling the same function over and over again untill the .map is done iterating, but if you wrote it in the component's return, every time the .map iterate over the next item you'll be creating a new function.
Regarding the question, I'd recommend the second way, clean/readable code is always preferable.
P.S. try to use the unique message id instead of the index.

How to refactor props data being passed in react js?

I'm passing data props to two components which are exactly the same like this :
export default function Home({ data }) {
return (
<Layout title="Home Page">
<CarouselMobile
aig={data.aig}
audi={data.audi}
bt={data.bt}
francetelecom={data.francetelecom}
hkt={data.hkt}
kelly={data.kelly}
mobinnet={data.mobinnet}
orange={data.orange}
pccw={data.pccw}
sap={data.sap}
tata={data.tata}
teleperformance={data.teleperformance}
wf={data.wf}
/>
<CarouselDesktop
aig={data.aig}
audi={data.audi}
bt={data.bt}
francetelecom={data.francetelecom}
hkt={data.hkt}
kelly={data.kelly}
mobinnet={data.mobinnet}
orange={data.orange}
pccw={data.pccw}
sap={data.sap}
tata={data.tata}
teleperformance={data.teleperformance}
wf={data.wf}
/>
</Layout>
);
}
How can I refactor this data code which is being passed to both components so it doesn't look messy like this ?
Just spread it if the names are equal.
<CarouselMobile {...data} />
<CarouselDesktop {...data} />
If not you can also partially spread props with the same name in your data object and the component, and apply other props which have different names.
<CarouselMobile {...data} differentProp={differentProp} />
<CarouselDesktop {...data} anotherDifferentProp={anotherDifferentProp} />
Or based on the comments, you can also pass the entire data object like below.
<CarouselMobile data={data} />
But the point here is that you would access attributes in CarouselMobile with data.aig for instance like below.
const CarouselMobile = ({ data }) => {
const { aig, audi, /* other props */ } = data;
};
Here, you can create a common object with these props and send that object to both components.
Or you can send data prop to the components and use there directly.
Another way, send data prop by using spread operator.

JSX callback without passing individual props

I wonder if there is a way to replicate same usage of js callback's in JSX like:
[1,2,3].map(console.log) // Here map iterator takes care of passing values to iteratee.
Is there a similar way to pass component like that, ie. rather then this:
<List data={data} renderItem={item => <Post {...item} />} />
something like this:
<List data={data} renderItem={<Post/>} />
Ahh ok, just found out you can use components as plain js functions:
<List data={data} renderItem={Post} />
You can, but speaking from experience this leads to pain and bad design later on once these components grow and requirements change. The List component becomes more esoteric, and it's hard to find where things should be.
For instance, if you wanted to do something as simple as render a sponsored Post, the code becomes needlessly complex:
// Index.js
<List data={data} renderItem={Post} renderSponsoredItem={SponsoredPost} />
// List.js
function List({ data, renderItem, renderSponsoredItem }) {
<div className={styles.list}>
data.map(dataItem => {
if (dataItem.sponsored) {
return renderSponsoredItem(dataItem);
} else {
return renderItem(dataItem);
}
});
</div>
}
...and in a way, this ends up looking not like React at all, and doesn't harness the ease-of-use and readability thats possible with React.
An alternative could look something like:
// Index.js
<List>
{data.map(dataItem => {
if (dataItem.sponsored) {
return <SponsoredPost post={dataItem} />;
} else {
return <Post post={dataItem} />
}
})
</List>
// List.js
function List({ children }) {
<div className={styles.list}>
{children}
</div>
}
I've found the first form to lend itself to unreadable code and unworkable, unreadable components in the long term, and the latter to be much easier to read/work with.

Trouble iterating/mapping props

I'm trying to develop a simple application in which any number of tasks will be rendered as cards. I'm passing them as props, schemed like so:
taskList: [{
taskID: 1,
taskTitle: 'Task 1',
taskDescription: 'Description 1',
completed: true
}]
By logging the props in the TaskCard component, I can see the list arrives exactly like that. If I try and log something such as props[0].taskDescription, it'll successfully return "Description 1", like so:
export default function TaskCard(props) {
return(
<div className="task-card" draggable>
<h3> Test </h3>
{ props[0].taskDescription } // this actually works
</div>
)
}
I can't, however, map or calculate the length of the props to iterate through props.
What am I doing wrong in terms of iteration? Is it a flawed architecture in terms of componentization?
To render a list of TaskCards, you need to do the mapping of taskList outside that component like so:
{taskList.map(task => <TaskCard task={task} />)}
and then TaskCard would render using the passed task props:
TaskCard(props) {
// use props.task.taskDescription, etc.
}
First, thank you all who contributed with your insights in the comments for the original posting. So yes, apparently passing an array as a prop is troublesome. I may not have resolved in the most efficient way, but here's what it has come down to so far. Use the image in the original post to guide yourself.
I have resolved the issue the following way:
UserArea.jsx
Contains a state that logs the user, username, a list containing pending tasks pendingList and a list containing complete tasks completedList;
It renders a header, the PendingArea and CompleteArea component passing {...this.state} to each of them (the full component is an object).
render(){
return(
<div className="user-area-container">
<div className="profile-header">
<h2>{ this.state.userName }</h2>
{ this.state.email }
</div>
<PendingArea {...this.state} />
<CompletedArea {...this.state} />
</div>
)
}
PendingArea.jsx and CompleteArea.jsx
Here I made the filtering, passing only the equivalent mapping to each of the components. The code for the PendingArea component is as follows:
function PendingArea(props) {
var pendingTasks = props.pendingList.map(task => {
return <TaskCard {...task} />
});
return(
<div className="status-container">
<div className="status-title">
<h2>Pending tasks</h2>
</div>
<div className="status-modifier">
{ pendingTasks }
</div>
</div>
)
}
TaskCards.jsx
Finally, the TaskCard component uses the direct properties from the props:
export default function TaskCard(props) {
return(
<div className="task-card" draggable>
<h3> { props.taskTitle } </h3>
{ props.taskDescription }
</div>
)
}
This way I managed to properly render the task card in their due place. I hope this works for anyone reading this, as well.

Spread operator to pass all other props to a component. React.js

I'm having trouble understanding the spread operator when I want to pass all other props to a component.
Any help would be appreciated.
import React, { Fragment } from "react";
import SiteCard from "./SiteCard";
const SiteList = ({ sites }) => {
return (
<Fragment>
{sites.map((site) => {
return (
<SiteCard
key={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
otherSiteProps={...site} // how can I pass the site props here?
/>
);
})}
</Fragment>
);
};
export default SiteList;
You are almost there with the solution.
You need to pass it as otherSiteProps={{...site}}.
This is if you want to pass site as an object to otherSiteProps property of SiteCard.
If you want to spread site and have multiple props for component SiteCard you do it like this:
<SiteCard
key={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
{...sites}
/>
This in case that sites is an object. If site is an array, this wont work.
You just need to write:
<SiteCard
key={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
{...site} // how can I pass the site props here?
/>
But wait, why you're making so complicated? You can just use:
<SiteCard {...site} />
Now, in your SiteCard component use required props.
And if I were you, I would not have separated SiteCard component for this scenario. I would just write:
{sites.map((site) => {
return (
// everything here I will utilize in html.
);
})}

Categories

Resources