How to dynamic render child Array component in react - reactjs

I want to render dynamic component But I cannot able to render children component nextcomp[]. currently only first component Text rendering.
const item = [{
component: "text",
label: "Name",
Type: "text",
nextComp: [{
component: "checkbox",
label: "yes",
question: "Do you Play football",
type: "checkbox",
}, {
component: "text",
label: "Age",
Type: "text",
nextComp: [{
component: "checkbox",
label: "yes",
question: "Do you Play football",
type: "checkbox",
}, {
component: "text",
label: "Gender",
Type: "text",
}],
}],
}, ]
App.js I mapped like
{item.map(block => Components(block))}
I am rendering component like
const Components = {
Text: Text,
checkbox: Checkbox
};
export default block => {
if (typeof Components[block.component] !== "undefined") {
return React.createElement(Components[block.component], {
key: block,
block: block
});
}
return React.createElement(
() => <View>The component {block.component} has not been created yet.</View>,
{ key: block}
);
};

I think I sort of understand what you are going for here. I don't see where your recursive renderer function recurses on the block.nextComp array.
React.createElement
React.createElement(
type,
[props],
[...children]
)
Map the block.nextComp to this renderer and pass the result as the third argument.
const Components = {
Text: Text,
checkbox: Checkbox
};
const renderBlock = block => {
if (typeof Components[block.component] !== "undefined") {
return React.createElement(
Components[block.component],
{
key: block,
block: block
},
block.nextComp?.map(renderBlock) || null,
);
}
return React.createElement(
() => <View>The component {block.component} has not been created yet.</View>,
{ key: block}
);
};
export default renderBlock;
Update
I think you've a typo in your Components object, "Text" isn't a key referenced in your item array objects. Update the key to match usage.
const Components = {
text: Text,
checkbox: Checkbox
};
To make rendering the item array a little more clear, you might want to give the imported recursive renderer a more meaningful/clear name. Map the item array the same way the block.nextComp was mapped.
import renderBlock from '../path/to/renderBlock';
...
{item.map(renderBlock)}
See this running codesandbox demo:

Related

Problems with React useMemo and useState. Does React useMemo create its own scope?

I want to optimize my code, and I am using the useMemo React hook to avoid re-computing some code within my component each time that a re-render occurs.
The code example here is very simple:
User clicks on Button, which filters the input data for the Table. The filteredData variable is updated and the UI is too.
This causes the useEffect hook to execute, and I can see that the console.log of filteredData is updated correctly (only shows filtered companies).
User clicks on "edit" inside table, which calls a function that is currently only printing the filteredData variable.
In this last step is where the problem occurs! This time the filtereredData displays all records, even though the variable has been reassigned (filtered) previously. See console logs in image:
Notice that the first console.log (from inside useEffect) prints only one record (the one visible). The next console.log, triggered by the user click, prints all records (this is incorrect).
I did some debugging, and the reason for this is the useMemo hook! If I remove it, then everything works and the variable gets displayed correctly. I don't understand why this is the case, because the onDeleteCompany function is declared outside of the useMemo hook.
Is this behavior correct? Can someone explain why this occurs and if I am using the useMemo hook incorrectly? If this is the expected behavior, is there any other way I can avoid re-computing the Column assignation?
Here is the minimum code you need to reproduce this problem:
import {Table, Dropdown, Button} from 'antd';
import type { MenuProps } from 'antd';
import {useState, useMemo, useEffect} from "react";
import {DownOutlined} from "#ant-design/icons";
var rows = [{
key: 4,
name: 'The Company',
contact: 'Test Cotnact 1',
email: 'fredrik.com',
phone: '999 999 999',
active: true,
},
{
key: 1,
name: 'Test 2',
contact: 'Test Contact 2',
email: 'test.com',
phone: '999 999 999',
active: true,
}]
export default function Operations() {
const [filteredData, setFilterData] = useState(rows)
const [neverChange, setNeverChange] = useState(true)
const items: MenuProps['items'] = [{label: 'Click', key: 1}]
//The useMemo here is used so as to no not re-compute the Columns each time the component is re-rendered
const columns = useMemo(() => {
return [
{
title: 'Editar',
key: 'edit',
dataIndex: 'edit',
render: (_:any, company: any, index: any) => (
<div className="pl-1">
<Dropdown menu={{items, onClick: (e) => {onDeleteCompany(company.key, index)}}} placement="bottomLeft">
<DownOutlined style={{fontSize: '1rem'}} />
</Dropdown>
</div>
)
},
{
title: 'Empresa',
dataIndex: 'name',
key: 'name',
},
{
title: 'Nombre de contacto',
dataIndex: 'contact',
key: 'contact',
},
{
title: 'Email de contacto',
dataIndex: 'email',
key: 'email',
},
{
title: 'Teléfono de contacto',
dataIndex: 'phone',
key: 'phone',
}
];
}, [neverChange])
useEffect(() => {
console.log("filteredData update: " + JSON.stringify(filteredData,null,4))
}, [filteredData])
const onDeleteCompany = (key: number | string, idx: number) => {
console.log("onDeleteCompany filteredData: " + JSON.stringify(filteredData,null,4))
}
const filterData = (value: string) => {
let filtered:any[] = [];
if(value) {
filtered = filteredData.filter((obj:any) => obj.name.includes(value))
setFilterData(filtered)
}
}
return (
<>
<Table size="middle" pagination={{pageSize: 12}} columns={columns} dataSource={filteredData} />
<Button onClick={(e) => {filterData('Company')}}></Button>
</>
)
}

how to use map in react typescript

Currently using a react in typescript.
make a separate file and manage it.
I want to do map work on the `card component
parameter on the map wrong?
definition wrong?
Can I get some advice...
Card.tsx
import { LIST_DATA, ListData } from './data';
const Card = () => {
return(
{LIST_DATA.map(({ id, title }: ListData[]) => {
return <S.InfoTitle key={id}>{title}</S.InfoTitle>;
})}
)
}
data.tsx
export interface ListData {
id: number;
title: string;
}
export const LIST_DATA: ListData[] = [
{
id: 0,
title: 'drawing',
},
{
id: 1,
title: 'total',
},
{
id: 2,
title: 'method',
},
{
id: 3,
title: 'material',
},
];
When you map, each item inside the map as argument is only one item from the original array. In your case you have an array of ListData but when you map each argument is only one of this ListData item type, therefore you have to change ListData[] to ListData as such:
import { LIST_DATA, ListData } from './data';
const Card = () => {
return(
{LIST_DATA.map(({ id, title }: ListData) => {
return <S.InfoTitle key={id}>{title}</S.InfoTitle>;
})}
)
}
Otherwise to answer the main question: map in typescript is the same as map in javascript, you just have to ensure you input the right type in order to "secure" your app.

How to passe value from dropdown made by semantic ui in react

I have problem with this select by semantic ui. I made object with values I mapping it, state is changing but value in Select is not updating - if I put props as default value, it is like hard coded, and not changing at all. So what i can do (e target value doesnt work) to pass the selected icon to change props state? Should i use hook?
import React from 'react';
import { Dropdown } from 'semantic-ui-react';
const Dropdown = () => {
let icons = [
{
"v1": "home",
"ico": "home"
},
{
"v1": "cart",
"ico": "cart"
},
{
"v1": "folder",
"ico": "folder"
},
{
"v1": "group",
"ico": "group"
},
{
"v1": "dollar",
"ico": "dollar"
},
{
"v1": "users",
"ico": "users"
},
{
"v1": "briefcase",
"ico": "briefcase"
},
{
"v1": "globe",
"ico": "globe"
},
{
"v1": "calendar",
"ico": "calendar"
},
{
"v1": "favorite",
"ico": "favorite"
},
];
const options = icons.map(({ v1, ico }) => ({ value: v1, icon: ico }))
return(
<div>
<Dropdown
text='Add icon'
icon='add circle'
floating
labeled
button
className='icon'
value={prop.icon}
options={options}
name='icon'
></Dropdown>
</div>
)
}
You can pass an onChange function to the component that receives two arguments: event and result. The result argument is an object that has a key value with the selected value of the dropdown. You then need to use the useState hook to save the selected value in the component state.
Hence, you could do something like this:
import {useState} from "react"
const [value, setValue] = useState("")
const handleChange = (e, result) => {
// result.value is the selected value from the Dropdown
setValue(result.value)
}
<Dropdown
onChange={handleChange}
/>

React combo doesnt select proper value

I'm totally new in react and I am trying to create add/edit form with combo box in it. I saved data in database but now when I loaded the form there is no selected value in "bank" combo. Looks like combo gets default value "Select" and I don't know where is the problem. My .net backend returns proper bank id from database and company.bankId also has value.
interface IProps {
closeAddEditCompanyModal: any,
cancelClick: any,
saveClick: any,
isVisible: boolean,
currentCompany: CompanyModel,
}
interface IState {
company: CompanyModel;
validationMessage: string,
isLoading: boolean,
activeIndex: number;
selectedIndex: number;
banks: Array<BankModel>;
isVisibleAddEditBankDialog: boolean;
}
handleChangeSelect = (key, e) => {
const company: CompanyModel = { ...this.state.company};
company[key] = e.value;
this.setState({ company: { ...company } });
};
<div style={{ minHeight: '50px' }}>
<FormControl>
<InputLabel htmlFor="active-label">Banks</InputLabel>
<Select
name="bankId"
options={this.state.banks.map( bank => ({
value: bank.bankId,
label: bank.bankName
}))}
onChange={(e) => this.handleChangeSelect('bankId', e)}
value={company && company.bankId ? company.bankId : ''}
>
</Select>
</FormControl>
import { useState } from "react";
import Select from "react-select";
function App() {
const [companyId, setCompanyId] = useState();
const letterChange = (data) => {
console.log(data);
company[data.value] = data.value;
// you can do here whatever you wish to do
// setCompanyId(data);
};
const banks = [
{ value: "bank1", label: "bank1" },
{ value: "bank2", label: "bank2" },
{ value: "bank3", label: "bank3" },
{ value: "bank4", label: "bank4" },
{ value: "bank5", label: "bank5" },
{ value: "bank6", label: "bank6" },
{ value: "bank7", label: "bank7" },
];
const company = [{
bank1: { value: "company1", label: "company1" },
bank2: { value: "company2", label: "company2" },
}];
return (
<>
<div className="App">
<Select
name="bankId"
options={banks}
onChange={letterChange}
value={companyId}
></Select>
</div>
</>
);
}
export default App;
I am still not clear what you are trying to achieve here. But here is a small piece of snippet that might help you.
Couple of things that i would like to point out in your code.
Inside your onChange handleChangeSelect you are passing a hardcoded value by passing bankId as string. I don't know if thats intentional.
You don't have to loop through the array inside options. Rather you can just pass the whole array itself.
This is just a suggestion but if you are already using material ui package you can use the select from material ui itself. This is just to avoid using extra packages unless and until there is a need for it.

How can I expand the option without using the arrow button in antd select?

I would like to know if this is possible in antd tree select. As you can see in the image. How can I expand the option without using the arrow button? I just want to click the word "Expand to load" and then drop down options will show.
I was playing around with their code in codesandbox.io.
This is what I came up with: https://codesandbox.io/s/loving-frog-pr1qu?file=/index.js
You'll have to create a span node when populating the title of your tree elements, where the span elements will listen for a click event.
const treeData = [
{
title: <span onClick={() => expandNode("0-0")}>0-0</span>,
key: "0-0",
children: [
{
title: <span onClick={() => expandNode("0-0-0")}>0-0-0</span>,
key: "0-0-0",
children: [
{
title: "0-0-0-0",
key: "0-0-0-0"
},
{
title: "0-0-0-1",
key: "0-0-0-1"
},
{
title: "0-0-0-2",
key: "0-0-0-2"
}
]
},
{
title: <span onClick={() => expandNode("0-0-1")}>0-0-1</span>,
key: "0-0-1",
children: [
{
title: "0-0-1-0",
key: "0-0-1-0"
},
{
title: "0-0-1-1",
key: "0-0-1-1"
},
{
title: "0-0-1-2",
key: "0-0-1-2"
}
]
},
{
title: "0-0-2",
key: "0-0-2"
}
]
},
{
title: <span onClick={() => expandNode("0-1")}>0-1</span>,
key: "0-1",
children: [
{
title: "0-1-0-0",
key: "0-1-0-0"
},
{
title: "0-1-0-1",
key: "0-1-0-1"
},
{
title: "0-1-0-2",
key: "0-1-0-2"
}
]
},
{
title: "0-2",
key: "0-2"
}
];
Then use a custom function to set the expandedKeys state and pass it as a prop to the Tree component. I tried using the filter method on the previous state for removing keys but it kept giving me an error so I fell back to using the for loop.
const expandNode = (key) => {
setAutoExpandParent(false);
setExpandedKeys((prev) => {
const outArr = [];
if (prev.includes(key)) {
for (let i = 0; i < prev.length; i++) {
if (prev[i] !== key) {
outArr.push(prev[i]);
}
}
return outArr;
} else {
prev.push(key);
return prev;
}
});
};
Note: I used antd's "Controlled Tree" example as the template and progressed from there.
Update
As I was using this solution for a project of mine, I found out a more robust method.
Instead of individually overriding the title properties on the data array, it is possible to override the titleRender prop on the tree component. titleRender is available from antd v.4.5.0.
Style the span tags as inline-block with width and height set to 100%. This makes the entire node block (instead of just the span text) listen for the onClick event when the tree has a prop of blockNode={true}.
Got rid of the for loop and successfully used filter method instead in the toggle expand method. This is more performant than looping.
I created a custom ClickExpandableTree component.
import React, { useState } from "react";
import { Tree } from "antd";
const ClickExpandableTree = props => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const toggleExpandNode = key => {
setExpandedKeys(prev => {
const outArr = [...prev];
if (outArr.includes(key)) {
return outArr.filter(e => e !== key);
} else {
outArr.push(key);
return outArr;
}
});
setAutoExpandParent(false);
};
const onExpand = keys => {
setExpandedKeys(keys);
setAutoExpandParent(false);
};
return (
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
titleRender={record => (
<span
key={record.key}
onClick={() => toggleExpandNode(record.key)}
style={{ display: "inline-block", width: "100%", height: "100%" }}
>
{record.title}
</span>
)}
{...props}
/>
);
};
export default ClickExpandableTree;

Resources