While looking through our code base, I found code that looks a bit like this:
const Carousel = ({ items }) => {
return (
<CarouselOuter>
{items.map((item) => (
<CarouselItemWrapper>
<CarouselItem key={item.key}>
...
</CarouselItem>
</CarouselItemWrapper>
)}
</CarouselOuter>
);
}
Notice that the key prop is on CarouselItem, not CarouselItemWrapper, the component that's directly returned from items.map. This seems to work fine, and there are no warnings in the console, but it runs counter to every example I've seen using map in React.
I want know if there's a good argument (specifically in regards to performance) for rearranging the code with the key as shown below, or if this is just a stylistic choice:
const Carousel = ({ items }) => {
return (
<CarouselOuter>
{items.map((item) => (
<CarouselItemWrapper key={item.key}>
<CarouselItem>
...
</CarouselItem>
</CarouselItemWrapper>
)}
</CarouselOuter>
);
}
Side note: CarouselOuter, CarouselItem, and CarouselItemWrapper are all styled-components, but I doubt that's relevant.
Related
I have been asked to solve a problem using any front-end framework ( reactjs in my case ) . Basically there is a form for adding a car and there can be say a type of car like 'SUV','semitruck','racecar' and so on...
In the form there needs to be different components rendered based on the type of car you want to add ( e.g. SUV has 2 inputs , racecar has 5 inputs ) how can i dynamically render these components and get their input values without doing if statements?
So pretty much i want to avoid doing this :
{typeSelection == "SUV" && (
<SVUInput
size={sizeInput}
changeSize={(str) => setSizeInput(str)}
/>
)}
{typeSelection == "Bus" && (
<WeightInput
Weight={weightInput}
changeWeight={(str) => setWeightInput(str)}
/>
)}
{typeSelection == "Semitruck" && (
<DimensionInput
wheels={wheels}
length={length}
changeWheels={(n) => setWheels(n)}
changeLength={(str) => setLength(str)}
/>
)}
I tried doing this but doesnt work ( im guessing react doesnt re-render here )
const [dynamicInputs, setDynamicInputs] = React.useState<any>({
SUV: <SUVInput size={sizeInput} changeSize={(str) => setSizeInput(str)} />,
bus: <BusInput weight={weightInput} changeSize={(n) => setSizeInput(n)} />,
semitruck: <SemitruckInput wheels={wheelsInput} changeWheels={(n) => setWheelsInput(n)}
color={colorInput} changeColor={(n) => setColorInput(n)} />,
});
Instead the above code although renders the component , the input does not change when i type into it, it remains blank , i assume it doesnt trigger react to re-render the state
So pretty much instead of making many if statements that would slow down the front-end , Im trying to make it dynamic so that it only takes O(1) time to render the correct form inputs.
I pretty like your solution because that's the thing I'm used to do really often. Your issue is about storing the components inside useState, because they are initialized there when component mounts and they just stay in the same state for the whole component lifetime. The solution is pretty simple as well - just move it out of the state so they do react to state and props changes.
https://codesandbox.io/s/frosty-wozniak-xcy2bv?file=/src/App.js:114-536
export default function App() {
const [size, setSize] = useState(0);
const [currentComponent] = useState('SUV');
const components = {
SUV: <SuvComponent size={size} />,
};
return (
<div className="App">
<input onChange={(e) => setSize(e.target.value)} />
{components[currentComponent]}
</div>
);
}
it works if i implement the UseState method and then have a useEffect to update it like so :
React.useEffect(() => {
setDynamicInputs({
SUV: (
<SUV size={sizeInput} changeSize={(str) => setSizeInput(str)} />
),
bus: (
<WeightInput
Weight={weightInput}
changeWeight={(str) => setWeightInput(str)}
/>
),
});
}, [dynamicInputs]);
There are some movie cards that clients can click on them and their color changes to gray with a blur effect, meaning that the movie is selected.
At the same time, the movie id is transferred to an array list. In the search bar, you can search for your favorite movie but the thing is after you type something in the input area the movie cards that were gray loses their style (I suppose because they are deleted and rendered again based on my code) but the array part works well and they are still in the array list.
How can I preserve their style?
Search Page:
export default function Index(data) {
const info = data.data.body.result;
const [selectedList, setSelectedList] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<CustomSearch
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
</div>
<div className={style.card_container}>
{info
.filter((value) => {
if (searchTerm === '') {
return value;
} else if (
value.name
.toLocaleLowerCase()
.includes(searchTerm.toLocaleLowerCase())
) {
return value;
}
})
.map((value, key) => {
return (
<MovieCard
movieName={value.name}
key={key}
movieId={value._id}
selected={selectedList}
setSelected={setSelectedList}
isSelected={false}
/>
);
})}
</div>
<div>
<h3 className={style.test}>{selectedList}</h3>
</div>
</main>
Movie Cards Component:
export default function Index({ selected, movieName, movieId, setSelected }) {
const [isActive, setActive] = useState(false);
const toggleClass = () => {
setActive(!isActive);
};
useEffect(()=>{
})
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
selected.splice(selected.indexOf(e.target.id), 1);
console.log(selected);
} else {
selected.push(e.target.id);
console.log(selected);
console.log(e.target);
}
setSelected([...selected]);
toggleClass();
};
return (
<div>
<img
className={isActive ? style.movie_selected : style.movie}
id={movieId}
name={movieName}
src={`images/movies/${movieName}.jpg`}
alt={movieName}
onClick={pushToSelected}
/>
<h3 className={style.title}>{movieName}</h3>
</div>
);
}
I can't directly test your code so I will assume that this is the issue:
Don't directly transform a state (splice/push) - always create a clone or something.
Make the setActive based on the list and not dependent. (this is the real issue why the style gets removed)
try this:
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
// filter out the id
setSelected(selected.filter(s => s !== e.target.id));
return;
}
// add the id
setSelected([...selected, e.target.id]);
};
// you may use useMemo here. up to you.
const isActive = selected.includes(movieId);
return (
<div>
<img
className={isActive ? style.movie_selected : style.movie}
id={movieId}
name={movieName}
src={`images/movies/${movieName}.jpg`}
alt={movieName}
onClick={pushToSelected}
/>
<h3 className={style.title}>{movieName}</h3>
</div>
);
This is a very broad topic. The best thing you can do is look up "React state management".
As with everything in the react ecosystem it can be handled by various different libraries.
But as of the latest versions of React, you can first start by checking out the built-in tools:
Check out the state lifecycle: https://reactjs.org/docs/state-and-lifecycle.html
(I see in your example you are using useState hooks, but I am adding these for more structured explanation for whoever needs it)
Then you might want to look at state-related hooks such as useState: https://reactjs.org/docs/hooks-state.html
useEffect (to go with useState):
https://reactjs.org/docs/hooks-effect.html
And useContext:
https://reactjs.org/docs/hooks-reference.html#usecontext
And for things outside of the built-in toolset, there are many popular state management libraries that also work with React with the most popular being: Redux, React-query, Mobx, Recoil, Flux, Hook-state. Please keep in mind that what you should use is dependant on your use case and needs. These can also help you out to persist your state not only between re-renders but also between refreshes of your app. More and more libraries pop up every day.
This is an ok article with a bit more info:
https://dev.to/workshub/state-management-battle-in-react-2021-hooks-redux-and-recoil-2am0#:~:text=State%20management%20is%20simply%20a,you%20can%20read%20and%20write.&text=When%20a%20user%20performs%20an,occur%20in%20the%20component's%20state.
I'm following the react js tutorial, and I keep running into this issue
import React from "react";
import NewsCard from "../NewsCard/NewsCard";
const NewsCards = ({ articles }) => {
return (
<div>
{articles.map((article, i) => {
<NewsCard />;
})}
</div>
);
};
export default NewsCards;
Seems like your articles does not have default value as [].
You can change as follow. And you should give key attribute when using map function.
const NewsCards = ({ articles }) => {
const data = articles ? articles : []
return (
<div>
{data.map((article, i) => {
<NewsCard key={article.id}/>;
})}
</div>
);
};
Probably articles is not initialized when you try to map throught it. Try this:
{articles?.map((article, i) => {
<NewsCard />;
})}
OR
{articles && articles.map((article, i) => {
<NewsCard />;
})}
</div>
That way you will first make sure if articles exist
This means that the articles prop is undefined.
There are several ways to solve this. The first and easiest way is by implementing the following logic:
{articles?.length ? articles.map((article, i) => <NewsCard />) : "There are no articles here."}
Another way to solve this is by implementing React proptypes - you can read about this here.
Third and "hardest" (but probably best) way to solve this is by using a static type checking tool. Flow comes to mind, but you can use TypeScript too.
If you still need help, just like what the previous answers said, make sure that articles is initialized/defined by using the && operator to make that check. Also, based upon what you wrote, the map method is returning undefined since you specified a function body (using the function body bracket notation {} ) without a return statement. So instead write the map method like this:
<div>
{articles && articles.map((article, i) => <NewsCard />)}
</div>
or like this:
<div>
{articles && articles.map((article, i) => {
return <NewsCard />
})}
</div>
The first example implies an implicit return since an arrow function is being used and a function body is not present (there are no function body brackets { }).
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.
What I want to do, using map, is pretty plain.
I want to call this:
<Striper size={3} text={"Hey everybody!"}/>
To get this:
<>
<Stripe>
<Stripe>
<Stripe>
Hey everybody!
</Stripe>
</Stripe>
</Stripe>
</>
I tried this way, but it fails:
const Striper = (props) => {
const contentTop=props.sizer.map((item)=> <Stripe>)
const contentBottom=props.sizer.map((item)=> </Stripe>)
return (
<div>
{contentTop}
{contentBottom}
</div>
)
}
Basically only this works (which isn't what I want):
const contentTop = props.sizer.map((item)=> <Stripe></Stripe>)
How could I get what I want?
The solution ended up being really simple (thank you, Emile): use .reduce.
As it says in the documentation about reduce, it's really useful when you need only one thing returned. And that's what I needed.
As I said in a comment:
What I want to return from <App size={2} text="Hello"/> is really
<Stripe><Stripe>Hello</Stripe></Stripe>, but because I have to
return a whole object, the closest I can come with map is
<Stripe>Hello</Stripe><Stripe>Hello</Stripe>.
So instead, use reduce.
This is the solution, verified to work. (Note: I'm being somewhat handwavey about size={3} when it's actually an array, because I know how to do that stuff, it isn't my question but you can see my implementation in the answer below).
const Striper = (props) => {
const content = props.sizer.reduce(
(total, currentValue) => <Stripe color={props.colors}>{total}</Stripe>
)
return (
<div>
{content}
</div>
)
}
And as it's actually called:
const arr = Array(6).fill("this is my text!");
return (
<div>
<Striper sizer={arr} colors={colours}/>
</div>
);
I guess you can achieve something like with this approach:
import React from 'react';
import { render } from 'react-dom';
const Stripe = () => <span> – </span>
const App = ({ size, text }) => {
const arrFromNum = Array.from(Array(size), (x, i) => i + 1)
return (
<React.Fragment>
{arrFromNum.map(x => <Stripe />)}
{text}
{arrFromNum.map(x => <Stripe />)}
</React.Fragment>
)
}
render(<App size={4} text="Hello" />, document.getElementById('root'));
Does this answer your question?
Here's one possible way of many different possibilities based on your scenario:
<Striper size={3} text={"Hey everybody!"}/>
export default ({ size, text }) => <><Stripe size={size} /> {text} <Stripe size={size} /></>;
export default ({ size }) => <>{ Array(size).fill().map(_ => <h3>Stripe!</h3>) }</>;
https://stackblitz.com/edit/react-tnqv2k