I am new to react and I am trying to build a folder tree component in react.
I am following this tutorial - link
you can view the code here - sandbox
I am confused about how destructing is happening in this case
const Tree = ({ data, children }) => {
const isImparative = data && !children;
return (
<StyledTree>
{isImparative ? <TreeRecursive data={data} /> : children}
</StyledTree>
);
};
Tree.File = File;
Tree.Folder = Folder;
const structure = [
{
type: "folder",
name: "src",
childrens: [
{
type: "folder",
name: "Components",
childrens: [
{ type: "file", name: "Modal.js" },
{ type: "file", name: "Modal.css" }
]
},
{ type: "file", name: "index.js" },
{ type: "file", name: "index.html" }
]
},
{ type: "file", name: "package.json" }
];
export default function App() {
return (
<div className="App">
<Tree data={structure} />
</div>
);
I am confused in this particular line
const Tree = ({ data, children }) => {
How is the destructing happening her?
You are destructing { data, children } from structure array.
How does it automatically decide what to pick inside structure for data and what to pick for children?
There are no field named data and children as well. So how is this working here?
When you define a component (here its Tree) it might expect props. Inside the props there is all the different properties which we are passing for example for the below the data is a prop.
<Tree data={structure} />
So, The destructuring is done for the prop. if you remove the object for the const Tree = ({ data, children }) and write prop instead of it and check you will see the different props passed to the component.
If it was like below, there would be another component called children which would contain the <div>My child</div>. This is useful for making reusable components.
<Tree data={structure}><div>My child</div><Tree/>
Related
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;
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} />
</>
)}
I have a code as below
useEffect(() => {
{
updateLocalColumns();
}
}, []);
const updateLocalColumns = () => {
let templocalColumns = [...localColumns];
templocalColumns[0]..defaultSortOrder = "descend";
setlocalColumns(templocalColumns);
}
const orderContext = useContext(OrderContext);
const { data, columns } = orderContext;
const [localColumns, setlocalColumns] = useState([...columns]);
return (
<div>
<Table columns={columns} dataSource={data} />
{console.log(columns)}
</div>
)
'columns' is a state that is obtained by using hooks, I am trying to make a copy over local component that is 'localColumns" and thus modify from there without interfering the global 'columns'
I was expecting the original columns to remain as its original state, however when I print out, the original columns is the same as 'localColumns' which is the modified one.
Expected Output
localColumns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
columns: [
{
title: "Username",
dataIndex: "username",
key: "username",
}
]
Current Wrong Output
localColumns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
columns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
I am suspecting the problem is when I initate the state, I did a referencing.
The way I did copy was from this link and this link
I would try a simpler code:
//Import this class in the proper place
//import { clone } from 'ramda';
const orderContext = useContext(OrderContext);
const { data, columns } = orderContext;
let localColumns0 = clone(columns) //This is done to make a deep copy of "columns"
localColumns0[id]['defaultSortOrder']="descend"
const [localColumns, setlocalColumns] = useState(localColumns0);
return (
<div>
<Table columns={columns} dataSource={data} />
{console.log(columns)}
</div>
)
If you don't want only the copy of the state, maybe you need to look how to do the deep copy of an array, after you make a deep copy of the estate you can pass the state to your personal component with <PersonalCom {...deepCopyStateObj}/> and the PersonalCom need a constructor where you receive the props, like the following code.
constructor(props) {
super(props);
}
Or something equivalent in JSX syntax.
However make a deep copy of the state and pass the object to component don't look like, because you need to clone the state each time that your app refresh the UI, and react refresh very frequently the UI, for this reason, I suggest cloning the propriety that you need inside the personal component.
I have a stencil component that takes an array of objects to display. I am trying to pass that array in from my react app, and have been following this example to do that. If i log the component with the array passed in, i can see that it appears to be formed correctly.
The actual component I built to create the reference to handle the array is where my problem is, and I just can't figure out where I made the mistake, as there is an issue with the example. My component inside my react app, which returns the stencil component is as follows
const ActionBar: React.FC<{ pageActions: pageActionsObj }> = ({ pageActions }) => {
const elementRef = useRef(null);
useEffect(() => {
(elementRef.current).pageActionList = pageActions;
}, [pageActions]);
return <pw-actionbar ref={elementRef}></pw-actionbar>;
};
and I try to use it as a regular component as such
<ActionBar pageActions={pageActionsObj}/>
Where the array that im passing as the prop is
export const pageActionsObj = [
{
name: "Users",
page: "partner",
icon: "pw-i-filled-multiple-users",
onpage: false
},
{
name: "Billing",
page: "billing",
icon: "pw-i-filled-billing",
onpage: false
},
{
name: "Helpdesk",
page: "help-desk",
icon: "pw-i-filled-help",
onpage: false
},
{
name: "My Profile",
page: "profile",
icon: "pw-i-filled-user",
onpage: true
},
{
name: "Sign Out",
page: "sign-out",
icon: "pw-i-filled-sign-out",
onpage: false
},
];
I very strongly believe that the way the component is set up, paired with the way I am passing in the array as a prop is causing the issue. I have been stuck for hours. What am i missing?
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" }
]);
}, []);