ReactJS: Creating a "dynamic" render of Row and Col - javascript

I have an array of object with "id", "name", "value" that I pass to a component and it divided in row and col in this way:
export const RenderDetailRow = props => {
const columns = [];
props.content.forEach((content, idx) => {
columns.push(
<div className="col-sm py-3" key={`item_${idx}`}>
<b>{content.name + ': '}</b>
<Input type="text" name={content.name} id={content.id} readOnly value={content.value} />
</div>
);
if ((idx + 1) % props.display[0].number === 0) {
columns.push(<div className="w-100"></div>);
}
});
return (
<div className="row" style={{ margin: 30 }}>
{columns}
</div>
);
};
I have two kind of problem, the first:
Each child in a list should have a unique "key" prop.
I have inserted the key but I have this error.
If the number of field is odd I have a long Input, it is possible to create a empty field o something like this?
For example Date and Created By has every one 1/2 of the space, while Last Modified has 2/2. How can I do?
Thank you
EDIT.
props.display[0].number is only a number that i pass (for example 2,3,4) to obtain the number of cols
EDIT2:
Example of Array that I pass:
const Content = [
{
id: 'id',
name: 'ID',
value: realm.id,
},
{
id: 'realmId',
name: 'ID Realm',
value: realm.realmId,
},
{
id: 'name',
name: 'name',
value: realm.name,
}
]
const Display = [
{
number: 2,
},
];
so my render is:
render(
<RenderDetailRow content={Content} display={Display} />
)

For 1, you are missing key in if block
Try point 2:
function chunkArray(array, size) {
if(array.length <= size){
return [array]
}
return [array.slice(0,size), ...chunkArray(array.slice(size), size)]
}
export const RenderDetailRow = props => {
const columns = props.content.map((content, idx) => {
return (
<div
key={`item_${idx}`}
className="col-sm py-3"
>
<b>{content.name + ': '}</b>
<Input type="text" name={content.name} id={content.id} readOnly value={content.value} />
</div>
);
});
const rows = chunkArray(columns, NUMBER_OF_ROWS);
return rows.map((row, index) => (
<div className="row" key={index} style={{ margin: 30 }}>
{row[index]}
{row[index].length - NUMBER_OF_ROWS !== 0
? // (render row[index].length - NUMBER_OF_ROWS) empty columns here
: null}
</div>
))
};

Related

Logic of updating an object in react with useState

I have a component renderRoyaltyAccount, that gets rendered x number of times depending on the input that sets royaltyAccount.
In this component I have 2 fields, one for the name of the account, and the second a percentage.
What I wanted to do is depending of the number of accounts to create, create an object with those two fields for each, example :
If he chooses to create two accounts , to have a the end (what I thought but could be not the best choice :) ) :
{
1: {
"account": "test1",
"percentage": 2,
},
2: {
"account": "test#",
"percentage": 0.5
}
}
I tried with a useState and updating it with onChange with inputs, but it was a mess LOL.
If anyone could help me with this state, and specially the logic with objects and hooks. Thank you
export default function FormApp() {
const [royaltyAccount, setRoyaltyAccount] = useState(1);
const [allAccounts, setAllAccounts] = useState ({
{
"account": "",
"percentage": 1,
},
})
const renderRoyaltyAccounts = () => {
let items = [];
for (let i = 0; i < royaltyAccount; i++) {
items.push(
<div key={i}>
<div>
<label>Royalty Account n° {i + 1}</label>
<input onChange={()=> setAllAccounts(???)} type="text"/>
</div>
<div>
<label>Royalty %</label>
<input onChange={()=> setAllAccounts(???)} type="text"/>
</div>
</div>
)
}
return items;
}
return (
<>
<label> Royalty account(s)</label>
<input onChange={(e) => { setRoyaltyAccount(e.target.value)}} type="number"/>
{
renderRoyaltyAccounts()
}
</>
)
}
Dynamically compute the allAccounts state array from the initial royaltyAccount state value. Add an id property to act as a GUID for each account object.
Create a handleRoyaltyAccountChange onChange handler to either append a computed diff of the current allAccounts array length to the new count value, or to slice up to the new count if less.
Create a handleAccountUpdate onChange handler to shallow copy the allAccounts state array and update the specifically matching account object by id.
Give the inputs a name attributeand pass the mappedallAccountselement object's property as thevalue` prop.
Code:
import { useState } from "react";
import { nanoid } from "nanoid";
function FormApp() {
const [royaltyAccount, setRoyaltyAccount] = useState(1);
const [allAccounts, setAllAccounts] = useState(
Array.from({ length: royaltyAccount }).map(() => ({
id: nanoid(),
account: "",
percentage: 1
}))
);
const handleRoyaltyAccountChange = (e) => {
const { value } = e.target;
const newCount = Number(value);
setRoyaltyAccount(newCount);
setAllAccounts((accounts) => {
if (newCount > accounts.length) {
return accounts.concat(
...Array.from({ length: newCount - accounts.length }).map(() => ({
id: nanoid(),
account: "",
percentage: 1
}))
);
} else {
return accounts.slice(0, newCount);
}
});
};
const handleAccountUpdate = (id) => (e) => {
const { name, value } = e.target;
setAllAccounts((accounts) =>
accounts.map((account) =>
account.id === id
? {
...account,
[name]: value
}
: account
)
);
};
return (
<>
<label> Royalty account(s)</label>
<input
type="number"
onChange={handleRoyaltyAccountChange}
value={royaltyAccount}
/>
<hr />
{allAccounts.map((account, i) => (
<div key={account.id}>
<div>
<div>Account: {account.id}</div>
<label>
Royalty Account n° {i + 1}
<input
type="text"
name="account"
onChange={handleAccountUpdate(account.id)}
value={account.account}
/>
</label>
</div>
<div>
<label>
Royalty %
<input
type="text"
name="percentage"
onChange={handleAccountUpdate(account.id)}
value={account.percentage}
/>
</label>
</div>
</div>
))}
</>
);
}

How to not show the first item on a array in a map?

I receive an array like this from backend:
[
{
id: 0,
name: "John",
language: "Enlgish"
},
{
id: 1,
name: "Chris",
language: "Spanish"
},
{
id: 2,
name: "Bastian",
language: "German"
}
]
So I display the languages from this array in a table, and to do that I map through them.
I don't want to show the first language on the first object of this array
Parent.js
const [language, setLanguage] = useState ([])
useEffect(() => {
axios
.get('api').then((res) => {setLanguage(response.data.languages)})
}, [])
Child.js
return(
{language.map((lang, i) => {
return (
<tr key={"item-" + i}>
<td>
<div>
<input
type="text"
value={
lang.language
? lang.language.shift()
: lang.language
}
</div>
</td>
</tr>
))}
)
So what I have tried by far is the shift method which removes the first item of an array, but it didn't work.
This error happened :TypeError: lang.language.shift is not a function
How can I fix this?
Use the index
{language.map((lang, i) => {
(i > 0) && (
return (
......

How change the format of checkbox in react final form

I implemented the form through react final form
const products= [
{ label: "T Shirt", value: "tshirt" },
{ label: "White Mug", value: "cup" },
{ label: "G-Shock", value: "watch" },
{ label: "Hawaiian Shorts", value: "shorts" },
];
<>
<Form
onSubmit={onSubmit}
render={({ handleSubmit, pristine, invalid, values }) => (
<form onSubmit={handleSubmit} className="p-5">
{products &&
products.map((product, idx) => (
<div className="custom-control custom-checkbox" key={idx}>
<Field
name="state"
component="input"
type="checkbox"
value={product.value}
/>
<label
className="custom-control-label"
htmlFor={`customCheck1-${product.value}`}
>
{product.label}
</label>
</div>
))}
<button type="submit" disabled={pristine || invalid}>
Submit
</button>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
</>
If I am selecting checkboxes the checked values are showing array of values like [tshirt,cup] but I need to show the array of objects like [ { label: "T Shirt", value: "tshirt" }, { label: "White Mug", value: "cup" }]
I tried so many ways but I have not any luck. Please help me to out of this problem
values will always be the array consisting of the "value" attribute for the Field tag.
If you want the object from the products array,you could do the following
console.log(values.map(val => products.find(p => p.value === val)))
or create an object first via reduce & then use it.
const obj =products.reduce((map,p)=>{
map[value]=p
return map
},{})
console.log(values.map(v => productMap[v]))
add a onchange method to you input. the method must take value of product.
const [selectedProducts, setSelectedProducts] = useState([]);
const handleChange = (value) =>{
const itemToAdd = products.find(product => product.value === value);
const index = selectedProducts.findIndex(item => item.value === value);
if (index === -1){
setSelectedProducts([...selectedProducts, products[index]])
}else {
const data = [...selectedProducts];
data.splice(index, 1);
setSelectedProducts(data);
}
}
some change to jsx
<Field
onChange = {handleChange}
name="state"
component="input"
type="checkbox"
value={product.value}
checked = {selectedProducts.findIndex(item => item.value === value)!== -1}
/>

Multiple array check react js

I have an array of objects which I'm rendering by section - see title of each object "Price", "Sectors and Charges" etc.
This populates a mini modal where users can select options to update rendered columns basically a filter.
The selection of the items are working however if I make a selection of the first item "0" all sections with the first option are selected.
How can I store the selection from each object into the selectedOptions array?
Please note I'm using react js and styled components, I've not added the styled component code.
Data:
const columnsData = [
{
title: 'Price',
options: [
{
label: 'Daily Change'
},
{
label: 'Price'
},
{
label: 'Price Date'
},
{
label: 'Volatility Rating'
}
],
},
{
title: 'Sectors and Charges',
options: [
{
label: 'Sector'
},
{
label: 'Asset Class'
},
{
label: 'AMC'
},
],
},
{
title: 'Cumulative Performance',
options: [
{
label: '1 month'
},
{
label: '6 months'
},
{
label: '1 year'
},
],
},
]
Code:
const EditColumns = ({active, onClick}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const update = () => {
onClick();
}
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList>
{item.options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionList>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
export default EditColumns;
Your section lists are all sharing the same state variable, so any changes will be applied to all of them. You could fix this either by constructing a more complex state object which more closely resembles the structure of columnsData, or making each SectionList its own component with its own state. What you decide to do will depend on the degree to which the EditButtons component actually needs access to the whole state.
The second approach might look something like this:
const EditColumns = ({active, onClick}) => {
const update = () => {
onClick();
}
return (
<Wrap onClick={() => update()}>
<CTA>
<SVG src="/assets/svgs/btns/edit.svg" />
<span>Columns</span>
</CTA>
{active &&
<Dropdown>
<Head>
<span className="title">Edit Columns</span>
<span>Select the columns you would like to see</span>
</Head>
<Body>
{columnsData.map((item, i) => {
return (
<Section key={i}>
<SectionHead>
<span className="title">{item.title}</span>
<span>Select all</span>
</SectionHead>
<SectionList options={item.options}/>
</Section>
)
})}
</Body>
</Dropdown>
}
</Wrap>
)
}
const SectionList = ({options}) => {
const [selectedOptions, setSelectedOptions] = useState([0, 1, 2]);
const updateSelection = (z) => {
setSelectedOptions(selectedOptions.includes(z) ? selectedOptions.filter(j => j !== z) : [...selectedOptions, z]);
}
return (
<SectionListContainer>
{options.map((child, z) => {
const selected = selectedOptions.includes(z);
return (
<li key={z} className={classNames({selected})} onClick={() => updateSelection(z)}>
<span>{child.label}</span>
</li>
)
})}
</SectionListContainer>
)
}

React data components table does not render HTML

https://github.com/carlosrocha/react-data-components package does not allow sending html into a td cell. See:
My goal is hyperlink to that product.
My use is:
import React from 'react';
var DataTable = require('react-data-components').DataTable;
import PlainTable from './PlainTable'
class ReduxDataTable extends React.Component {
constructor(props) {
super(props);
}
processHeaders(){
var columns = [];
for (let i = 0; i < this.props.data.headers.length; i++){
var header = this.props.data.headers[i];
var item = {title: header, prop: header};
columns.push(item);
}
return columns;
}
render() {
var dataList = this.props.data.data;
console.log("datalist is", dataList);
console.log("datalist length is", dataList.length);
var headerList = this.processHeaders();
if(dataList.length > 2) {
return (
<DataTable
keys="name"
columns={headerList}
initialData={dataList}
initialPageLength={20}
initialSortBy={{ prop: headerList[0].title, order: 'descending' }}
pageLengthOptions={[ 20, 60, 120 ]}
/>
);
}
else {
return (
<PlainTable
headers={headerList}
rows={dataList}
/>
);
}
}
}
export { ReduxDataTable as default };
then just
return (
<div className="card">
<h2 className="style-1">Detailed Report</h2>
<br/>
<h2 className="style-1:after">Data about products </h2>
<ReduxDataTable data={data}/>
</div>
)
Plain table is a <table> in case there's few products.
The package does not show any "htmlTrue" option, as searching "html" show nothing useful. I'm getting the same issue with any html at all:
I'm not opposed to forking it, but is there a simple way to use this package and declare html here?
I didn't use that component, but looking through the code, it seems that you can use a render function to do what you need. See here: https://github.com/carlosrocha/react-data-components/blob/3d092bd375da0df9428ef02f18a64d056a2ea5d0/src/Table.js#L13
See the example here https://github.com/carlosrocha/react-data-components/blob/master/example/table/main.js#L17
Relevant code snippet:
const renderMapUrl =
(val, row) =>
<a href={`https://www.google.com/maps?q=${row['lat']},${row['long']}`}>
Google Maps
</a>;
const tableColumns = [
{ title: 'Name', prop: 'name' },
{ title: 'City', prop: 'city' },
{ title: 'Street address', prop: 'street' },
{ title: 'Phone', prop: 'phone', defaultContent: '<no phone>' },
{ title: 'Map', render: renderMapUrl, className: 'text-center' },
];
return (
<DataTable
className="container"
keys="id"
columns={tableColumns}
initialData={data}
initialPageLength={5}
initialSortBy={{ prop: 'city', order: 'descending' }}
pageLengthOptions={[ 5, 20, 50 ]}
/>
);
Try adding the render property to your dataList. Maybe something like this
var dataList = this.props.data.data;
for (let i=0; i<dataList.length; i++)
dataList[i].render = function(val, row) {return (
<a href={row.href}>row.title</a>
)}

Categories