how to add unique key using uuid in react js? - arrays

I read a comment from someone here in StockOverflow who talks about React keys and said that
'React expects STABLE keys, meaning you should assign the keys once and every item on your list should receive the same key every time, that way React can optimize around your data changes when it is reconciling the virtual DOM and decides which components need to re-render. So, if you are using UUID you need to do it at the data level, not at the UI level',
and I want to ask if anyone know how to apply this in a real code where we have for example a context component that have an array of objects and another component that maps through this array, how can we apply this using uuid() or any other package.

Although it is not a common requirement for you to generate id on FE, it happens some times, so using uuid is a really good way of doing that. It is easy and implementation is quick.
I made an example for you here how to do it:
import { useEffect, useState } from "react";
import { v1 } from "uuid";
import "./styles.css";
const items: { name: string; id?: string }[] = [
{
name: "Name1"
},
{
name: "Name2"
},
{
name: "Name3"
},
{
name: "Name4"
},
{
name: "Name5"
},
{
name: "Name6"
}
];
export default function App() {
const [itemList, setItemList] = useState<{ name: string; id?: string }[]>([]);
useEffect(() => {
setItemList(items.map((item) => ({ ...item, id: v1() })));
}, []);
return (
<div className="App">
{itemList.map((item) => (
<div key={item.id}>
{item.name} - {item.id}
</div>
))}
</div>
);
}
In this example your array is empty at the beginning and after first useEffect gets populated by items with uuid generated ids:
And code sandbox code

You can install react-uuid
import uuid from 'react-uuid'
const array = ['one', 'two', 'three']
export const LineItem = item => <li key={uuid()}>{item}</li>
export const List = () => array.map(item => <LineItem item={item} />)
or you can use crypto.randomUUID() directly without pckgs

Related

Next JS - how to give object a unique id?

I am trying to give my object a unique id by using Math.random, but I am getting the error below. If I change the Math.random to a integer like 4, the error goes away but I need the id to be unique.
Unhandled Runtime Error
Error: Text content does not match server-rendered HTML.
See more info here: https://nextjs.org/docs/messages/react-hydration-error
const data = [
{
id: Math.random(),
sentence: 'hello there',
},
{
id: Math.random(),
sentence: 'bye!',
},
]
export default function Index(props) {
return (
<div>
<h1>{data[0].id}</h1>
<h1>{data[0].sentence}</h1>
<h1>{data[1].id}</h1>
<h1>{data[1].sentence}</h1>
</div>
)
}
As explained in my comment and one of the answers, the problem is happening because Next.js pre-renders pages, therefore, the random number generated by Math.random() when pre-rendering the page on the server doesn't match the random number generated client-side when hydration occurs.
I'm not quite sure what you're trying to achieve by setting random ids to what seems to be "dummy data" (You could do it manually with constant values that will match both server and client-side) but I understand that this might be a simplified example.
You have a couple of options, typically you'd want to move any random generation code/logic inside a useEffect hook so it executes on the client-side only.
Another solution would be to move your "dummy data" and the rendering of this data to a separate component, let's call it DummyComponent:
const data = [
{
id: Math.random(),
sentence: 'hello there',
},
{
id: Math.random(),
sentence: 'bye!',
},
]
const DummyComponent = () => (
<>
<h1>{data[0].id}</h1>
<h1>{data[0].sentence</h1>
<h1>{data[1].id}</h1>
<h1>{data[1].sentence}</h1>
</>
)
export default DummyComponent
And import it dynamically on your page disabling ssr:
import dynamic from 'next/dynamic'
const DummyComponent = dynamic(() => import('../components/DummyComponent'), {
ssr: false,
})
export default function Index(props) {
return (
<div>
<DummyComponent />
</div>
)
}
Use the uuid npm package to generate unique id's.
import { v4 } from "uuid";
const data = [
{
id: v4(),
sentence: 'hello there',
},
{
id: v4(),
sentence: 'bye!',
},
]
First, the page is rendered on the server, then returned to the client to rehydrate, two times initializing data would cause different ID values. This is called hydration mismatch, which is solved by useId in React 18
But using NextJS, you could solve this problem by initializing data on server to keep it consistent
export default function Index(props) {
return (
<div>
<h1>{props.data[0].id}</h1>
<h1>{props.data[0].sentence}</h1>
<h1>{props.data[1].id}</h1>
<h1>{props.data[1].sentence}</h1>
</div>
);
}
export async function getServerSideProps(context) {
return {
props: {
data: [
{
id: Math.random(),
sentence: 'hello there',
},
{
id: Math.random(),
sentence: 'bye!z',
},
],
}, // will be passed to the page component as props
};
}
Stackblitz demo
References
getServerSideProps

AntD Tree: need help! can't pass react element as icon OR title for antd tree

I'm using the AntD tree and I have a react element that I want to pass as either an icon or a title because it has custom styling. Due to it being IP I can't share too much code, but my question is:
how can I pass a react element (see below i.e. generic name) as either a title or icon and have antD tree render it?
i.e. this is what I want to pass as a prop to the icon or title
import React from 'react';
const genericName = (props) => {
// code uses props to get some infor for Color
// cant share code due to proprietary reasons
// but it is not needed for this question
const colorHTML = getColor(Color);
return (
<div>
<div className={`colors from`}>${colorHTML}</div>
{pin}
</div>
);
};
export default genericName;
in my console you can see node.icon is a typeof react.element. I want to target that and just pass the prop into antD tree as either title or icon
i.e.
return (
<Tree
icon={node.icon}
/>
)
I've searched and similar answers were given before antD forbid the use of children and strictly allows treeData. All examples I see only use strings in titles/icons, but since antD documentation is very limited, I need to know if my use case is possible. Right now, for the life of me I can't understand why it doesn't populate.
Thank you in advance.
It should definitely work to put a JSX component as title within treeData. Take a look at this snippet, I added a Icon here in one of the titles:
import React from 'react'
import { RightCircleOutlined } from '#ant-design/icons'
type Props = {}
import { Tree } from 'antd';
import type { DataNode, TreeProps } from 'antd/es/tree';
const treeData: DataNode[] = [
{
title: <span>{<RightCircleOutlined />} parent</span>, //icon added here
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
disabled: true,
children: [
{
title: 'leaf',
key: '0-0-0-0',
disableCheckbox: true,
},
{
title: 'leaf',
key: '0-0-0-1',
},
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [{ title: <span style={{ color: '#1890ff' }}>sss</span>, key: '0-0-1-0' }],
},
],
},
];
const Demo: React.FC = () => {
const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
console.log('selected', selectedKeys, info);
};
const onCheck: TreeProps['onCheck'] = (checkedKeys, info) => {
console.log('onCheck', checkedKeys, info);
};
return (
<Tree
checkable
defaultExpandedKeys={['0-0-0', '0-0-1']}
defaultSelectedKeys={['0-0-0', '0-0-1']}
defaultCheckedKeys={['0-0-0', '0-0-1']}
onSelect={onSelect}
onCheck={onCheck}
treeData={treeData}
/>
);
};
export default Demo;

How to render custom elements for each item in an object (Map data structure) in React TS?

I've been using this method to render multiple custom elements from an array but this is my first time doing it using a map data structure. It compiles fine but renders nothing. I've set up a codesandbox here.
import "./styles.css";
import React from "react";
import ImageGrid from "./ImageGrid";
interface OnlineImage {
id: string;
url: string;
}
export default function App() {
const [onlineImages, setOnlineImages] = React.useState<Map<string, OnlineImage[]>>();
React.useEffect(() => {
let onlineImageMap = new Map<string, OnlineImage[]>();
// fake api call
onlineImageMap.set("randomImageSet1", [
{ id: "1", url: "https://picsum.photos/200" },
{ id: "2", url: "https://picsum.photos/200" }
]);
onlineImageMap.set("randomImageSet2", [
{ id: "1", url: "https://picsum.photos/200" },
{ id: "2", url: "https://picsum.photos/200" }
]);
// set state
setOnlineImages(onlineImageMap);
}, []);
return (
<div className="App">
<div>Below should render my custom ImageGrid for each item in map...</div>
<>
{onlineImages?.forEach((imageSet, category) => {
return (
<>
<div>Image Category: {category}</div>
<ImageGrid images={imageSet} />
</>
);
})}
</>
</div>
);
}
Hi Samuel: I think you should first convert the map to an Array and then use the Array.prototype.map method which actually returns a copy of the original array with your function applied to it.
As you probably already figured out the return statement does nothing within a forEach function (to be more exact it only stops the execution of the code but does not bring back anything into the outer context).
If you really want to use forEach you'll have to use an array or Object to catch it then use Object.entries/.map to iterate over it
const myMap = new Map(Object.entries({a: 1, b: 2}))
const myMapCaptureList = []
myMap.forEach((value, key) => {
myMapCaptureList.push([key, value])
}
// then you can use myMapCaptureList
myMapCaptureList.map(([key, value]) => <div>{key}: <span>{value}</span></div>);
But I would suggest that it is much easier to use the very helpful Array.from method that can help convert a Map into an array of key/value pair entries: [[key1, val1], [key2, val2]]. It is essentially equivalent to running Object.entries(someObject).
{Array.from(onlineImages || []).map(([category, imageSet]) => {
return (
<>
<div>Image Category: {category}</div>
<ImageGrid images={imageSet} />
</>
);
})}
You are using the .forEach method - which returns nothing, instead use .map that is identical but does return things
{onlineImages?.map((imageSet, category) =>
<>
<div>Image Category: {category}</div>
<ImageGrid images={imageSet} />
</>
)}

React FC Context & Provider Typescript Value Issues

Trying to implement a global context on an application which seems to require that a value is passed in, the intention is that an API will return a list of organisations to the context that can be used for display and subsequent API calls.
When trying to add the <Provider> to App.tsx the application complains that value hasn't been defined, whereas I'm mocking an API response with useEffect().
Code as follows:
Types types/Organisations.ts
export type IOrganisationContextType = {
organisations: IOrganisationContext[] | undefined;
};
export type IOrganisationContext = {
id: string;
name: string;
};
export type ChildrenProps = {
children: React.ReactNode;
};
Context contexts/OrganisationContext.tsx
export const OrganisationContext = React.createContext<
IOrganisationContextType
>({} as IOrganisationContextType);
export const OrganisationProvider = ({ children }: ChildrenProps) => {
const [organisations, setOrganisations] = React.useState<
IOrganisationContext[]
>([]);
React.useEffect(() => {
setOrganisations([
{ id: "1", name: "google" },
{ id: "2", name: "stackoverflow" }
]);
}, [organisations]);
return (
<OrganisationContext.Provider value={{ organisations }}>
{children}
</OrganisationContext.Provider>
);
};
Usage App.tsx
const { organisations } = React.useContext(OrganisationContext);
return (
<OrganisationContext.Provider>
{organisations.map(organisation => {
return <li key={organisation.id}>{organisation.name}</li>;
})}
</OrganisationContext.Provider>
);
Issue #1:
Property 'value' is missing in type '{ children: Element[]; }' but required in type 'ProviderProps<IOrganisationContextType>'.
Issue #2:
The list is not rendering on App.tsx
Codesandbox: https://codesandbox.io/s/frosty-dream-07wtn?file=/src/App.tsx
There are a few different things that you'll need to look out for in this:
If I'm reading the intention of the code properly, you want to render OrganisationProvider in App.tsx instead of OrganisationContext.Provider. OrganisationProvider is the custom wrapper you have for setting the fake data.
Once this is fixed, you're going to run into an infinite render loop because in the OrganisationProvider component, the useEffect sets the organisations value, and then runs whenever organisations changes. You can probably set this to an empty array value [] so the data is only set once on initial render.
You're trying to use the context before the provider is in the tree above it. You'll need to re-structure it so that the content provider is always above any components trying to consume context. You can also consider using the context consumer component so you don't need to create another component.
With these suggested updates, your App.tsx could look something like the following:
import * as React from "react";
import "./styles.css";
import {
OrganisationContext,
OrganisationProvider
} from "./contexts/OrganisationContext";
export default function App() {
return (
<OrganisationProvider>
<OrganisationContext.Consumer>
{({ organisations }) =>
organisations ? (
organisations.map(organisation => {
return <li key={organisation.id}>{organisation.name}</li>;
})
) : (
<div>loading</div>
)
}
</OrganisationContext.Consumer>
</OrganisationProvider>
);
}
And the updated useEffect in OrganisationsContext.tsx:
React.useEffect(() => {
setOrganisations([
{ id: "1", name: "google" },
{ id: "2", name: "stackoverflow" }
]);
}, []);

How to format value of the React Select

I use "react final forms" and "react select".
I've organized an interface and functionality works, but I have one thing which I don't like.
React select requires options in next format:
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
and the value also should be set as { value: 'chocolate', label: 'Chocolate' }.
But for me is strange to have in my model of data (and send to the server also) the value like this - { value: 'chocolate', label: 'Chocolate' }, because I need only 'chocolate'.
Easy way is format the object into a single value after the form will be saved and format back from single value to the object before the rendering of the element. I know how to do it outside of form, but in this case I should solve this problem again and again for each select element separately.
What I would like to do:
Find a way how to set value of the react select as single value, like 'chocolate' instead of object.
OR
Create a wrapper for react select component and format value there when it sets (it's easy) and when the form get the value from this field (this I don't know how to do).
I will appreciate any help.
Method 1
With strings:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
const data = ["1", "2"];
function SingleStringReactSelect() {
const [option, setOption] = useState();
return (
<section>
<Select
onChange={option => setOption(option)}
value={[option]}
getOptionLabel={label => label}
getOptionValue={value => value}
closeMenuOSelect={false}
options={data}
/>
</section>
);
}
Method 2:
Created example:
https://codesandbox.io/s/react-codesandboxer-example-z57ke
You can use map in options and with Wrapper like SingleValueReactSelect
import React, { Fragment, useState } from "react";
import Select from "react-select";
const data = ["chocolate", "strawberry", "vanilla"];
export function SingleValueReactSelect(props) {
const [selectedItem, setSelectedItem] = useState();
return (
<Select
{...props}
value={selectedItem}
onChange={item => {
setSelectedItem(item);
props.onChange(item.value);
}}
options={props.options.map(item => ({ label: item, value: item }))}
/>
);
}
export default function AppDemo() {
return (
<Fragment>
<p>SingleValueReactSelect Demo</p>
<SingleValueReactSelect
isClearable
isSearchable
options={data}
onChange={item => {
alert(item);
}}
/>
</Fragment>
);
}
You can transform/filter the object data once you are sending the form data to server or another component.
Setting the value of the react-select as a single value will not show it as selected in the React-Select.

Resources