How to handle arrays in a Grommet DataTable? - javascript

I'm trying to use arrays in Grommet DataTable. My data looks like this :
{
customer: [
'BANANA',
'Banana',
'banana',
'republic of banana'
],
somethingelse: ['ABC','123','DEF']
}
In a regular Grommet Table , I'm able to use every cell by defining the first value from the array as title - for example customer[0] - and create an expandable arrow to show the rest of the data in 'customer' :
But I don't get how to do this on a cell basis for a Grommet DataTable ?
Here is the way I'm using it in the regular Grommet Table :
<TableCell scope="row" pad={{ left: '2px', righ: '3px' }}>
<TextInput name="tags" size="xsmall" />
</TableCell>
</TableRow>
{searchResults.length > 0 &&
searchResults.map((searchResult, index) => (
<TableRow key={index}>
<TableCell>
<Box direction="row">
<Text size="xsmall">{searchResult.customer[0]}</Text>
{searchResult.customer.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[index] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(index)}
/>
)}
</Box>
<Box>
{isExpanded[index] && listElements(searchResult.customer)}
</Box>
</TableCell>
Here is my Form , using DataTable :
return (
<Form value={formData} onSubmit={onSubmit} onChange={onChange}>
...
<DataTable
fill
border={{ body: 'bottom' }}
paginate
columns={columns}
data={searchResults}
select={select}
onClickRow={(e) => console.log(e.datum)}
onSelect={() => {}}
step={8}
rowDetails={(row) => { // I'm able to use rowDetails to expand and display some data , but how can I use this to 1. Use the [0] element of the array as title and 2. apply to all cells in the row/table.
for (const cell in row) {
// if (cell.length > 1) {
// return listElements(cell);
// }
console.log(cell);
}
}}
...
/>
...
</Form>
);

I was able to achieve that by using the render function and passing a CellElement to it, in which I have created my rules :
const columns = [
{
property: 'customer',
header: <FormField label="Customer" name="customer" size="xsmall" />,
render: (datum) => <CellElement val={datum.customer} />,
},
CellElement.js
import { Box, Text, Button } from 'grommet';
import { FormNext, FormDown } from 'grommet-icons';
import React, { useState } from 'react';
const CellElement = ({ val }) => {
const title = Array.isArray(val) ? val[0] : val;
const [isExpanded, setIsExpanded] = useState({});
const toggleOpen = (category) => {
setIsExpanded({
...isExpanded,
[category]: !isExpanded[category],
});
};
const listElements = (arr) => {
return arr.slice(1).map((el, index) => (
<Text key={index} size="xsmall">
{el}
</Text>
));
};
return (
<Box>
<Box direction="row">
<Text size="xsmall">{title}</Text>
{Array.isArray(val) && val.length > 1 && (
<Button
plain
hoverIndicator={false}
icon={
isExpanded[title] ? (
<FormDown size="18px" />
) : (
<FormNext size="18px" />
)
}
onClick={() => toggleOpen(title)}
/>
)}
</Box>
<Box>{isExpanded[title] && listElements(val)}</Box>
</Box>
);
};
export default CellElement;

Related

React props updating with useState?

I have the component below where I'm trying the build functionality to allow a user to update opening times of a store.
I'm passing the original opening times as a prop and creating some state using the props opening times for initial state. I want to use the new state to submit changes but if the user selects cancel the UI updates to reflect the original times.
I have most of the functionality working but for some reason my handler to update the state with the input change also seems to update the props value so it won't go back to the original value.
How can I stop the props updating and ensure only the allOpeningHours state is changed?
VIDEO: https://www.veed.io/view/c40bf9e8-7502-408a-ba6d-fd306dbf4b6f?sharingWidget=true
const EditStudioHours: FC<{ studio: Studio }> = ({ studio }) => {
const { value: edit, toggle: toggleEdit } = useBoolean(false)
const { value: submitting, toggle: toggleSubmitting } = useBoolean(false)
const [allOpeningHours, setAllOpeningHours] = useState([
...studio.openingHours.regularDays,
])
return (
<Box>
<Typography variant='h6' mt={2} gutterBottom>
Set standard hours
</Typography>
<Typography fontWeight='light' fontSize={14}>
Configure the standard operating hours of this studio
</Typography>
<Stack mt={3} spacing={2}>
{studio.openingHours.regularDays.map((hours, i) => (
<DayOfWeek
dow={daysOfWeek[i]}
openingHours={hours}
edit={edit}
i={i}
setAllOpeningHours={setAllOpeningHours}
allOpeningHours={allOpeningHours}
/>
))}
</Stack>
<Button
variant={edit ? 'contained' : 'outlined'}
onClick={() => {
toggleEdit()
}}
fullWidth
sx={{ mt: 2 }}
disabled={!edit ? false : submitting}
>
{submitting ? (
<CircularProgress size={22} />
) : edit ? (
'Submit changes'
) : (
'Edit'
)}
</Button>
{edit && (
<Button
onClick={toggleEdit}
variant={'outlined'}
sx={{ mt: 1 }}
fullWidth
>
Cancel
</Button>
)}
</Box>
)
}
export default EditStudioHours
const DayOfWeek: FC<{
openingHours: { start: number; end: number }
dow: string
edit: boolean
i: number
setAllOpeningHours: any
allOpeningHours: any
}> = ({ openingHours, dow, edit, i, setAllOpeningHours, allOpeningHours }) => {
const [open, setOpen] = useState(openingHours.end !== openingHours.start)
const handleOpenClose = () => {
open &&
setAllOpeningHours((ps: any) => {
const newHours = [...ps]
newHours[i].start = 0
newHours[i].end = 0
return newHours
})
setOpen((ps) => !ps)
}
const handleStart = (e: any) => {
setAllOpeningHours((prevState: any) => {
const newHours = [...prevState]
newHours[i].start = e.target.value
return newHours
})
}
const handleEnd = (e: any) => {
setAllOpeningHours((ps: any) => {
const newHours = [...ps]
newHours[i].end = e.target.value
return newHours
})
}
return (
<Box display='flex' alignItems='center' justifyContent={'space-between'}>
<Box display={'flex'} alignItems='center'>
<Typography width={150}>{dow}</Typography>
<FormGroup>
<FormControlLabel
control={
<Switch
disabled={!edit}
checked={open}
onChange={handleOpenClose}
/>
}
label='Open'
/>
</FormGroup>
</Box>
{open && (
<Box display={'flex'} alignItems='center'>
<TextField
disabled={!edit}
id={`${i}open`}
select
label='Open'
value={edit ? allOpeningHours[i].start : openingHours.start}
type='number'
sx={{ minWidth: 120 }}
size='small'
onChange={handleStart}
>
{openingOptions.map((option: { value: number; label: string }) => (
<MenuItem dense key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<Typography mx={2}>TO</Typography>
<TextField
disabled={!edit}
id={`${i}close`}
select
label='Close'
value={edit ? allOpeningHours[i].end : openingHours.end}
type='number'
sx={{ minWidth: 120 }}
size='small'
onChange={handleEnd}
>
{openingOptions.map((option: { value: number; label: string }) => (
<MenuItem dense key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
</Box>
)}
</Box>
)
}
The problem is that objects within the studio.openingHours.regularDays array still share the same reference, even though you copied the array itself.
When you use something like
newHours[i].start = e.target.value
You're still updating the original objects from props.
You can use Array.prototype.splice() to remove the object at index i and replace it with a new one
const day = newHours[i];
newHours.splice(i, 1, {
...day,
start: e.target.value,
});
Do this in each of your 3 handle functions.
Alternately, break all references when creating local state from props
const [allOpeningHours, setAllOpeningHours] = useState(
studio.openingHours.regularDays.map((day) => ({ ...day }))
);

How to fix current radio button to be clicked seprate for seprate question

I'm trying to render some questions to evaluate the old device price. From backend I'm receiving multiple questions and I have to give a radio button to the question for YES/NO to for the selection, But when I click One radio button other buttons also get clicked. How to fix this.
My code.
const [radioValue, setRadioValue] = useState('0')
const radios = [
{ name: 'Yes', value: '1' },
{ name: 'No', value: '0' }
And this is the rendering part:
{specialQuestLoading ? (
<Loader></Loader>
) : specialQuestError ? (
<Message color="danger"> {specialQuestError} </Message>
) : (
<>
{specialQuestions ? (
specialQuestions.map((question) => (
<Card classNames="mx-2 py-2" key={question.id}>
{question.question_brand === order.prod_brand ? (
<>
<Card.Title> {question.question}</Card.Title>
<Row>
{' '}
<Col md={4}> </Col>
<ButtonGroup className="mb-2">
{radios.map((radio, idx) => (
<ToggleButton
key={idx}
id={`radio-${idx}`}
type="radio"
variant="secondary"
name="radio"
value={radio.value}
checked={radioValue === radio.value}
onChange={(e) => setRadioValue(e.currentTarget.value)}
>
{radio.name}
</ToggleButton>
))}
</ButtonGroup>
<>
<br />
</>
</Row>
</>
) : (
<></>
)}
</Card>
))
) : (
<></>
)}
</>
)}
And this is the problem. Both get checked clicking the one.
Any body knows how to fix this, Or How can I fix this ?
You are using single state for every specialQuestions Card. So changing any of the ToggleButton affects every Card Element (specialQuestions).
You should split the Card Element to a separate Component and manage the radioValue state there.
export const Card = ({question}) => {
const [radioValue, setRadioValue] = useState('0')
const radios = [
{ name: 'Yes', value: '1' },
{ name: 'No', value: '0' }
return (
<Card classNames = "mx-2 py-2" key = {question.id} >
{question.question_brand === order.prod_brand ? (
<>
<Card.Title >
{question.question}
</Card.Title>
<Row>
{' '}
<Col md = {4}>< /Col>
<ButtonGroup className = "mb-2" >
{radios.map((radio, idx) => (
<ToggleButton
key = {idx}
id = {`radio-${idx}`}
type = "radio"
variant = "secondary"
name = "radio"
value = {radio.value}
checked = {radioValue === radio.value}
onChange = {
(e) => setRadioValue(e.currentTarget.value)
}
>
{radio.name}
</ToggleButton>
))
}
</ButtonGroup>
<><br /></>
</Row>
</>)
: ( <></>)
}
</Card>
)
}
And consume it as
specialQuestions.map((question) => (
<Card question={question} />
)

Accordion inside a Flatlist React native

I have an accordion inside a flatlist.
Here is the code i have :
const OptionList = (groupOption) => {
return (
<FlatList
data={groupOption.options}
keyExtractor={(result) => result.id.toString()}
renderItem={({ item, index }) => {
const clickedRadio = () => {
const selectedOption = { [item.question]: { ...item } };
setAnswers({ ...answers, ...selectedOption });
};
const status = isOptionSelected(item) ? true : false;
return (
<View key={index}>
<Radio
initialValue={status}
label={item.description}
onChange={() => clickedRadio()}
color="error"
/>
</View>
);
}}
/>
);
};
return (
<View style={styles.container}>
<Text style={{ fontWeight: "bold", fontSize: 16, color:"#6B24AA" }}>
{t("Choose an option/Scroll for more questions")}
</Text>
<FlatList
data={questions}
listKey={(item) => item.id.toString()}
keyExtractor={(result) => result.id.toString()}
renderItem={({ item, index }) => {
const data = [
{
title: item.description,
content: (<><OptionList options=
{item?.question_options}></OptionList></>)
}
];
const status = isOptionSelected(item) ? true : false;
return (
<View style={styles.groupOptions} key={index}>
<Accordion style={styles.accordion}
headerStyle=
{styles.headerStyle} contentStyle={styles.contentStyle}
dataArray={data}
icon={status ? <Icon name="circle"
family="Entypo" size={20} style={{marginLeft: -6,
color: "#6B24AA"}}/>
:
<Icon name="done"
family="MaterialIcons" size={20}
style={{marginLeft: -6}}/>}
expandedIcon={<Icon name="circle"
family="Entypo"/>}
opened={1}/>
</View>
);
}}
/>
</View>
);
The accordion content its anther flatlist component. It shows this error every time i click the accordion.
It shows this error :
VirtualizedList: Encountered an error while measuring a list's offset from its containing VirtualizedList.
at node_modules/react-native/Libraries/Lists/VirtualizedList.js:1411:10 in _scrollRef.measureLayout$argument_2
How can i fix this error? Is it the problem the other flatlist at the content of accordion
Please replace the OptionList component with the given below code.
OptionList
const OptionList = (groupOption) => {
return (
groupOption.map((item,index) => {
const clickedRadio = () => {
const selectedOption = { [item.question]: { ...item } };
setAnswers({ ...answers, ...selectedOption });
};
const status = isOptionSelected(item) ? true : false;
return (
<View key={index}>
<Radio
initialValue={status}
label={item.description}
onChange={clickedRadio}
color="error"
/>
</View>
)
})
);
};
please check and let me know , cheers !

Change switch value without change all of them

I'm a beginner and I don't know how to change switch value without change all of them. You can see the code about. I'm mapping a nested array on return. I'm confuse how to change the switch status individually. I've tried some solutions like change the value inside the array with an onchange event but it doenst reender the screen.
const [dtData, setDtData] = useState('release_data');
useFocusEffect(
React.useCallback(() => {
getManagerListDay(function(resultado) {
setDtData(resultado);
});
setInterval(() => {
getManagerListDay(function(resultado) {
setDtData(resultado);
});
toasted.showToast('Refresh');
}, 60000);
}, [])
);
const section = [];
for ( var i = 0, ii = dtData.length; i < ii; i++ ) {
if(i>0?dtData[i].dayDate != dtData[i-1].dayDate:true == true){
const items = [];
for ( var b = 0, bb = dtData.length; b < bb; b++ ) {
if(dtData[b].dayDate == dtData[i].dayDate){
items.push({
name: dtData[b].name,
worked: dtData[b].worked,
workedWeekDays: dtData[b].workedWeekDays,
KeyItemDriver: dtData[b].KeyItemDriver
});
}
}
section.push({
dayDate: dtData[i].dayDate,
driverAmount: dtData[i].driverAmountAccepted,
keySectionDay: dtData[i].keySectionDay,
data: items
});
}
}
return (
<Root>
<View style={{marginTop:15}}>
{section.map((obj, index) => {
return(
<List.Section
title={obj.dayDate}
titleStyle={{color:'black',fontWeight:'bold', fontSize: 16}}
style={{backgroundColor:'rgba(79,79,79,0.1)', marginTop:0, borderRadius:30, width:'90%',alignSelf:'center'}}
key={index}
id={obj.keySectionDay} >
<Button mode="contained" style={{borderRadius:70,width:40,backgroundColor:'#48D1CC', marginLeft:'80%'}}>
<Text style={{fontWeight:'bold', marginRight:50}}> {obj.driverAmount} </Text> </Button>
<FAB style={styles.fab} color='white' small={false} icon="database-export" onPress={() => console.log('Pressed')} />
<List.Accordion
title="DRIVERS"
key={index}
theme={{ colors: { primary: '#48D1CC' }}}
titleStyle={{color:'black', fontSize:14}}
left={props => <List.Icon {...props} icon="folder"/>}>
{obj.data.map((dts,index) => {
return(
<List.Item key={index} id={dts.KeyItemDriver} title={dts.name} titleStyle={{color:'black'}}
style={{borderTopColor:'white', borderTopWidth:1}}
left={props => <Switch {...props} color={dts.worked=='S'?'#48D1CC':'red'} value={dts.worked!=null?true:false}/>}
right={props => <Text {...props} style={{color:'#48D1CC',fontSize:18, marginTop:5}} > {dts.workedWeekDays} </Text>}
onPress={() => alert('Sou eu')}
/>
)
})}
</List.Accordion>
</List.Section>
);
})
}
</View>
</Root>
);
Never mind! I was thinking wrong about the steps process. I need.
I just update the information on the database and rerender the screen from there. What I think is correct. I must show what is commited.
Thanks!

ANTD Table getColumnSearchProps broken with nested object

Using codes from the example: https://ant.design/components/table/#components-table-demo-custom-filter-panel
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: text =>
this.state.searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[this.state.searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
Error Uncaught TypeError: Cannot read property 'toString' of undefined thrown when trying to pass nested values in the ANTD Table:
<Table bordered size='small' dataSource={data} rowKey='_id'>
....
<Column
title='Name'
dataIndex={['profile', 'name']}
{...this.getColumnSearchProps(['profile', 'name'])}
/>
....
</Table>
Here is how the structure of the data (dataSource) for the table:
[
{_id: 'xxx1', profile : { name : 'username1' }, roles: ['xxx1']},
{_id: 'xxx2', profile : { name : 'username2' }, roles: ['xxx2']}
]
As per outlined in the documentation: https://ant.design/components/table/#Migrate-to-v4 :
Besides, the breaking change is changing dataIndex from nest string
path like user.age to string array path like ['user', 'age']. This
help to resolve developer should additional work on the field which
contains ..
hence the dataIndex={['profile', 'name']}, but this is not the same case for the getColumnSearchProps.
Anyone can help?
Since the dataIndex could be an array now, you need to take care of that case as well.
An example is provided below:
You basically have to
Replace
record[dataIndex]
with
get(record, dataIndex) // import get from "lodash.get";
and
this.state.searchedColumn === dataIndex
with
isequal(this.state.searchedColumn, dataIndex) // import isequal from "lodash.isequal";

Categories