I need to replicate a component "n" times. To do that I used the lodash method times. The problem is that I need an index as a key for the components generated and It doesn't look like it has one.
I have the following code:
export const MyComponent: React.FC<{times: number}> = ({ times }) => {
return (
<>
{_.times(times, () => (
//I need a key={index} in this div
<div className="bg-white border-4 border-white md:rounded-md md:p-2 content-center my-4 shadow w-full">
</div>
))}
</>
);
};
This will return the component that is inside n times.
I tried to do a method that returns the component and set an index with useState, but it goes in an infinite loop. I thought to put a big random number as a key so it is extremely difficult to get the same, but I don't like that solution. I'd like to use this method because it is clean.
So what do you think I could do to give a to the component?
It's passed to you as a function parameter:
_.times(times, (index) => (blabla))
Related
I have a component that renders an array with .map() and a button that setState the shuffled version of it.
I'm trying to have it update every time I click that button but it doesn't.
const Home: NextPage = () => {
const [random, setRandom] = useState(students);
function randomize() {
const shuffle = students.sort(() => 0.5 - Math.random());
setRandom(shuffle);
}
return (
<>
<main className="container mx-auto flex min-h-screen flex-col items-center justify-center p-4">
<h1 className="text-6xl font-bold">This week's seat</h1>
<button onClick={randomize}>Shuffle</button>
{/* Seatings */}
<div className="seatbox">
{random.map((student) => (
<Seat key={student}>{student}</Seat>
))}
</div>
</main>
</>
);
};
I tried to log the random state, it got updated it just doesn't get rendered probably.
The Seat component is made with styled-components and the students array is an array of strings with the students name like this
const students = [
"Joe",
"Jason"
]
This sorts the array:
const shuffle = students.sort(() => 0.5 - Math.random());
But... it sorts the array in place and returns a reference to that same array. Since the reference equality hasn't changed (an array is a kind of object after all), React has no way to know that the state has been updated. It doesn't do any kind of deep compare, just an equality comparison for whatever is sent to it.
If you duplicate the array just before sorting (so as to not mutate it), the newly sorted array will be a new object reference and won't be "equal" to the previous state:
const shuffle = [...students].sort(() => 0.5 - Math.random());
I am using React and I have an array of data objects ("items"). I am trying to display this list of objects such that each object is its own component. So I'm calling "items.map" to map each item to a component and render it, like this:
return (
<Fragment>
<h1>Items</h1>
<Card.Group itemsPerRow={8} >
{items.length > 0 ? ( items.map( (_item) => (
<Fragment key={_item.key}>
<MyItem example={4} item={_item} />
</Fragment>
))
) : (<p>No items!</p>)}
</Card.Group>
</Fragment>
)
Here is the code for MyItem:
const MyItem = (example, item) => {
console.log(example);
console.log(item);
return (<Fragment>
<div style={{margin: "1em"}}>
<Card>
<Image src={item.image} wrapped ui={false} />
<Card.Content>
<Card.Header>{item.name}</Card.Header>
<Card.Description>
<p>Date Received: {item.dateReceived.toLocaleDateString()}</p>
</Card.Description>
</Card.Content>
</Card>
</div>
</Fragment>);
}
However, I'm getting an error saying that "item" is null in MyItem.js, while the list of items is definitely not null. Similarly, when I pass in the number 4 to the "example" prop, and I do "console.log(example)" it prints out the empty object "{}".
I should note that my page previously did display everything when both pieces of code were combined on the same page (i.e. there was no "MyItem" component). The reason I decided to make it a component was because I'm iterating over this map on multiple pages and it would be redundant code. This way, I can just call <MyItem item{_item} /> and it will display the same thing on every page that shows these items.
However, now that I have refactored my code to place the mapping inside of the component, it is no longer working. What am I doing wrong here? Is this even possible, or should I just go back to what I had before?
As mentioned in the above comment:
const MyItem = ({example, item})
This solves the problem.
This code gives me error when I try to map the state variable returned after calling useEffect hook.
On console, when I print the myAppointment it shows two values one is empty ([]) and other (Array[25]) I want to extract the petname values from this array . But getting error "map is not a function" Please look into it and help me!
function App() {
const [myAppointment, setmyAppointment] = useState([])
useEffect(() => {
fetch('./data.json')
.then(response=> response.json())
.then(result=>{
const apts=result.map(item=>{
return item
})
setmyAppointment(state=>({...state,
myAppointment:apts
}))
})**strong text**
},[])
console.log(myAppointment)
const listItem = myAppointment.map((item)=>{
return(
<div>{item.petName}</div>
)
})
return (
<main className="page bg-white" id="petratings">
<div className="container">
<div className="row">
<div className="col-md-12 bg-white">
<div className="container">
{/* {listItem} */}
<div><AddAppointment /></div>
<div><SearchAppointment /></div>
<div><ListAppointment /></div>
</div>
</div>
</div>
</div>
</main>
);
}
You have you state as an array. However when you try to update it you convert it into an object with key myAppointment which is the cause of your error.
You must update your state like
setmyAppointment(state=> [...state, ...apts]);
However since the state is initially empty and you only update it once in useEffect on initialLoad, you could also update it like
setmyAppointment(apts);
The problem is from the way you're setting your state after the data is fetched.
You're setting the state as an object by instead of an array.
Let's Take a look at the line where you set the state.
setmyAppointment(state=>({...state,
myAppointment:apts
}))
Using the spread operator with curly braces tells JavaScript compiler to spread an object, and obviously that's not what you want.
What you ought to do is instead of curly braces, use square brackets. That will tell the compiler you want to spread an array.
So it should be something like this .
setmyAppointment(state=>([...state
]))
And if I were you, I will do this a little bit different.
I will set the state like this
setmyAppointment(apt)
I hope this helps
I have this redux selector I've been working on in my React application. I've made good progress but I've hit a wall with this last issue i'm working on and I feel like it shouldn't be that difficult to solve but I'm struggling.
What I'm trying to achieve is after each item has been mapped, the next item must go to a new line.
export const vLineRejectionSelector = createSelector(
selectedVIdSelector,
linesSelector,
(id, lines) =>
lines
.filter(line => line.id === id)
.map(
(rejectString, index) =>
`Line: ${index + 1} ${rejectString.rejectReason}`
)
);
The only relevant code to look at in this is the map function. I want each item to go to a new line as its being mapped.
The output should look something like:
Line 1: Reject Reason One
Line 2: Reject Reason Two
Instead the output looks like:
Line1: Reject ReasonOneLine2: Reject Reason Two
This is being rendered in JSX as well
The value of this is passed around as a prop and gets rendered in the JSX like:
<Typography variant="body2">
{rejectReason}
{reasons && reasons.join(', ')}
</Typography>
Its value is {rejectReason}.
I would advise against composing JSX in your selector and rather just return the lines as an array as you are doing currently, but then map it to either a list or a simple <br /> joined list in the render() function. This keeps your selector more easily testable and also doesn't mix state selection concerns with presentational concerns.
E.g:
in your container
const mapStateToProps = (state) => {
return {
rejectReason: vLineRejectionSelector(state)
}
}
const SomeComponentContainer = connect(
mapStateToProps,
mapDispatchToProps
)(SomeComponent)
export default SomeComponentContainer
and then in your SomeComponents render function:
<Typography variant="body2">
{this.props.rejectReason.map((rejectReason) => <>Line: {index + 1} {rejectReason}<br /></>)}
</Typography>
Try to use <br /> tag at the end of each line.
Like Line: ${index + 1} ${rejectString.rejectReason}<br />
So I have this loop in my render:
{this.props.OneTestArrayProp.questions.map((q, i) =>
<div key={i} className="questions_div">
<div>Question: {q.question}</div>
<div>
)}
The this.props.OneTestArrayProp is loaded this way:
componentWillMount() {
if ( ! this.props.OneTestArrayProp.length ) {
this.props.dispatch(fetchOneTest(test_id));
}
}
But I'm getting:
Error: "this.props.OneTestArrayProp.questions.map" undefined
the error appears just for a second and then it disappears when Redux load the data. Of course the root of the problem is that half second when this.props.OneTestArrayProp is populated by the middleware thunk.
There is a way to indicate to React to "wait" before it does the render?
SOLVED
The solution provided by Red Mercury works pretty well, however I coded a more "redux-like" approach adding a new Array:
this.props.QuestionsArrayProp = [];
and linking it with its reducer:
QuestionsArrayProp: state.rootReducer.questions_reducer.QuestionsTestArrayProp
That way I get an initial, empty but existent array.
Check if it exists before mapping over its questions
{this.props.OneTestArrayProp &&
this.props.OneTestArrayProp.questions.map((q, i) =>
<div key={i} className="questions_div">
<div>Question: {q.question}</div>
<div>
)}
If this.props.OneTestArrayProp is undefined the expression will short-circuit and the map will never be executed