How to map through an array in React without ids? - arrays

I have a fairly standard array:
["value","value","value"]
If I want to iterate through it, each list item needs a unique key. But how would I do this in this case?
JSONs often come with an id -but in this case I don't have any id's, and the values also aren't unique, so I cannot just use them as the key.
{someArray.map(object => {
return (<span key=???> {object} </span>); })}

You can use the second argument of map, the index:
{someArray.map((object, i) => {
return (<span key={i}> {object} </span>); })}
As long as someArray remains the same during the lifetime of the component, this is fine; if not, it will cause more re-rendering than necessary when you move, insert or remove items, because all the props of the child change (but in this case you will not notice it at all).

each list item needs a unique key
It doesn't really. React's warnings here are very over-blown I feel. The unique keys are only needed if you need better performance, and I believe also some stuff about maintaining component state. It's worth noting that if you do dom.div(null, ["text"]) you will receive a warning, but if you do dom.div(null, ...["text"]) you will receive no warning despite the same array being passed in both cases. In the latter case React simply uses the index as the key, which is also totally fine in the former case but React warns about it for no real reason.
If the render function is stateless and you don't need better performance, just give them a key of their index in the array to silence the warning, or use the spread operator.

Related

React able to render arrays of elements without using .map?

I'm going through a book to better learn React and came across a surprising example. I previously thought that in order to render an array of anything in React, we need to map each element of the array, and pass them keys (I know keys aren't absolutely mandatory but a best practice so React knows which element to re-render upon change.)
But here is a working example of an array being rendered without needing to do anything special to it:
const Tail = ({number, children}) => (
<div>
Last {number} children:
{React.Children.toArray(children).slice(-number)}
</div>
)
Is this something special about children? Why is React able to render an array like this? There aren't even keys specified!
So React.Children provides utilities like toArray, map, forEach etc for dealing with the children props data structure.
In this case the toArray:
Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.
Here are the docs if you want to read more: https://reactjs.org/docs/react-api.html

why do I get "`key` is not a prop." in React?

So I have the code below, with bookList_object being a bunch of JSON objects I fetched from backend.
_renderBookList = () => {
const books = this.state.bookList_object.map((this_book, index) => {
return <BookList
body={this_book.body}
created_at={this_book.created_at}
key = {index}
/>
})
Now, I've learned that there has to be key when having multiple children. So I've used index parameter to assign each id to the key prop. But then I get this error:
"BookList: key is not a prop. Trying to access it will result in undefined being returned. If you need to access the same value within the child component, you should pass it as a different prop."
What have I done wrong?
Also, I already have id field in each of my object. Is there a way to use id instead of key?
Thank you very much in advance. :)
I recently had this Warning. It happened because I tried to assign a key to some elements in two different parts of the code. The warning pops up when the key prop is accessed a second time.
Find out where else the component is being assigned a key. Then choose only one place to assign a key.

How to test select option logic with React Testing Library

I'd like to test the selection logic of my Select component.
https://codesandbox.io/s/eager-violet-kkw0x?file=/src/App.js
I found this snippet to test a simulation.
test("simulates selection", () => {
const { getByTestId, getAllByTestId } = render(<Select />);
fireEvent.change(getAllByTestId("select"), { target: { value: 2 } });
let options = getAllByTestId("select-option");
expect(options[0].selected).toBeFalsy();
expect(options[1].selected).toBeTruthy();
expect(options[2].selected).toBeFalsy();
});
However, it fails with TypeError: Cannot read property 'map' of undefined. What's going on? Thank you.
Short answer: you need to make sure you do a conditional check to skip whenever your values are undefined. You can do so by sticking in values && just before values.map(...
Secondly, I really don't see the value of using a two dimensional array just for having an index. You can store a list of fruits in an array and iterate through with a simple map, passing in the value both as a value and as an key. If you want to go fancy with the key, you can even assign the number increment that is generated by the .map()'s index along with the value, here is the fancy version:
{values && values.map((index, value) => (
<option key={`${index}-${value}`} value={value} data-testid="select">
{value}
</option>
))}
The above would generate a key like fruit-1, anotherfruit-2, etc, however you can just go with the fruit name, whatever you do, make sure you don't just go with the index number as that is not a good practice. This is all when you have a simple array, as I said, you don't need a two dimensional one.
Moving forward, there are a lot of issues in your test and some problems in the code, so it won't work unless reimagining the whole code and test, however I give my best to try and explain some of the problems and point you to the right direction:
First line with the issue is:
fireEvent.change(getAllByTestId("select"), { target: { value: 2 } });
You want to select one element, that is the <select /> so you need to use getByTestId instead of getAllByTestId, you also got the id wrong, it is select-option.
The correct format looks like this:
fireEvent.change(getByTestId("select-option"), { target: { value: 2 } });
Just a quick note, while the above works, there are other, better ways of doing this, I recommend looking into the user-event library instead of fireEvent, but most importantly, use a getByRole instead of getByTestId, read why
Next problem is that you haven't passed in your props to your select component, therefore there are no <option> elements when rendered. You couldn't do this mistake with something like TypeScript as it would warn you, but JavaScript doesn't, so.. need to pass in the props:
const props = {
callback: jest.fn(),
values: [
"grapefruit", "coconut", "lime", "mango"
],
selected: 'lime'
}
const { getByTestId, getAllByTestId } = render(<Select {...props} />);
Moving forward, when collecting options, you used the wrong ID again (I think you mixed up the two) and I would also recommend using queryAll rather than getAll, this is because queryAll would return an empty array while getAll would throw an error.
const options = queryAllByTestId("select");
Finally your assertions are all wrong as well. Your option won't have a selected attribute what then you can boolean evaluate. Your option has two attributes data-testid and value.
I tried to give you the best answer to understand what is going on, but as the code stands, it's impossible to fix it without rethinking the whole app and test, like I mentioned above so here is my advice:
In the components:
Change to two dimensional array and use the value as the index.
You are not doing anything with the selected value, just console logging out, if you pass it back to your parent component, save it into the parent state and do whatever you want with it, Probably display somewhere - this is actually important for the test.
selected prop has a string, that will probably need to have the value that you pass back and save it to the state in the parent.
In the test:
React testing library is great because your tests resemble to how the users would use the app, therefore you should assert if the selected component appears where you want, instead of trying to inspect attributes, that would work the following way:
Make sure you render the component that will have both the select component and the component you will render the selected value to as text.
Make sure you pass in all props to the component you rendering with rtl
Simulate action (recommend the user-event library, but fireEvent will also work)
use the getByText to see if the value rendered and exists on the page. That would be something like: expect(getByText('fruitName')).toBeInTheDocument();
Finally, I recommend looking into the selector precedence (use getByRole where you can) and the difference between getBy and queryBy.

In which case react key prop warning can be ignored

A common pattern is generating react elements from array. In case of iterating over react components are there any cases where should/can ignore key prop warning?
Like can we ignore in case of element rendering static text etc?
Note: After rendering my elements aren't going to change, add new or remove.
const days = ['Sunday' 'Mon.........,,, 'Saturday'];
days.map(day => <div>{day}</div>);
You shouldn't be ignoring this warning.
Have a read of the doc: https://reactjs.org/docs/lists-and-keys.html#keys
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity
If you truely don't have any key like value to use, you can use the index of the array (assuming its coming from a statically ordered source) so that react can use the keys to identity what has changed.
const myArray = ["Some", "Static", "Stuff"]
const MyComponent = () => (
<div>
{myArray.map((item, i) => <div key={i}>{item}</div>)}
</div>
)

Are item keys required when rendering an array in React v15?

In earlier versions of React, I remember getting a warning when rendering an array without specifying a unique key for every item:
render() {
return (
<div>
{this.props.items.map(item => <span>{item.text}</span>)}
</div>
);
}
I understand that it is recommended to specify keys when rendering a dynamic list of items (where items can be added or removed) to help the reconciliation algorithm. I'd like to understand:
Why React doesn't warn anymore when keys are missing?
For static lists, is there a value in specifying item keys?
Official pointers will be appreciated.
Actually, I still see this warning message, and in official release notes there is no mention about this case
Example

Resources