Here in this code I am getting some array value, which is dynamic. On basis of that value I am generating UI. This value may change so according to that UI will change.
Now I have to upload all data on button click to server, I am getting confused.
Like if we have 3 fields static, I can create 3 states and by using that I can pass that value to the API.
But here as I am generating dynamic UI how can I take value and upload.
Below is the array value on basis of that I am generating dynamic form.
formFields: Array(8)
0:
key: "dateOfBirth"
label: "Date of Birth"
model: "model.dateOfBirth"
name: "dateOfBirth"
placeHolder: "DD/MM/YYYY"
position: "1"
required: "true"
type: "date"
__typename: "FormFields"
__proto__: Object
1:
key: "lastRechargedAmount"
label: "Last RCC Amt."
model: "model.lastRechargedAmount"
name: "lastRechargedAmount"
placeHolder: "Amount in Naira"
position: "2"
required: "false"
type: "text"
__typename: "FormFields"
__proto__: Object
2:
key: "stateOfOrigin"
label: "State of Origin"
model: "model.stateOfOrigin"
name: "stateOfOrigin"
placeHolder: "State of origin"
position: "3"
required: "true"
type: "parentSelect"
__typename: "FormFields"
__proto__: Object
3: {type: "childSelect", model: "model.lgaOfOrigin", label: "LGA of Origin", placeHolder: "Enter Lga of origin", key: "lgaOfOrigin", …}
4:
key: "frequentlyDialledNumbers1"
label: "Freq.Dialled No"
model: "model.frequentlyDialledNumbers"
name: "frequentlyDialledNumbers1"
placeHolder: "frequently dialled MSISDN"
position: "5"
required: "true"
type: "text"
__typename: "FormFields"
__proto__: Object
5: {type: "text", model: "model.frequentlyDialledNumbers", label: "Freq.Dialled No", placeHolder: "frequently dialled MSISDN", key: "frequentlyDialledNumbers2", …}
6: {type: "text", model: "model.frequentlyDialledNumbers", label: "Freq.Dialled No", placeHolder: "frequently dialled MSISDN", key: "frequentlyDialledNumbers3", …}
7: {type: "text", model: "model.frequentlyDialledNumbers", label: "Freq.Dialled No", placeHolder: "frequently dialled MSISDN", key: "frequentlyDialledNumbers4", …}
length: 8
The code below I am using to generate UI and on button press I am calling function validatUser(). On button press I have to take all values and send in this function.
class CustomerEvalidation extends Component {
constructor(props) {
super(props);
const { eValidationMasterData } = this.props;
this.state = {
selectedRow: -1,
disableBtn: true,
showSimSwapReason: false,
dateOfBirth: currentDate,
lastName: '',
lgaValue: eValidationMasterData.masterData.lga[0].code,
stateValue: eValidationMasterData.masterData.lga[0].state.name,
stateCode: eValidationMasterData.masterData.lga[0].state.code,
isModalVisible:false
};
}
componentWillUnmount() {
this.setState({
showSimSwapReason: false
});
}
lgaChanged = (key, val) => {
this.handleChangeLga({ field: "lgaValue" }, val);
};
handleChangeLga = (props, e) => {
const { lga } = this.props.eValidationMasterData.masterData;
let tempObj = this.state.lgaValue;
for (let i = 0; lga[i]; i++) {
if (lga[i].code == e) {
const stateData = lga[i].state;
this.setState({
stateValue: stateData.name,
stateCode: stateData.code
})
}
}
tempObj[props.field] = e;
this.setState({ lgaValue: e });
};
validatUser = () => {
const {dateOfBirth,lgaValue,stateCode}=this.state;
const validateUser = {
dateOfBirth: dateOfBirth,
lgaValue:lgaValue,
stateCode:stateCode
}
this.props.verifySecurityQuestions(validateUser);
}
onChangeExp = (e) => {
this.setState({
dateOfBirth: e
})
}
render() {
const { dateOfBirth, lastName, lgaValue, stateValue } = this.state;
const { isCamera, loading, error, securityQuestions, eValidationMasterData,validateUser } = this.props;
const { formFields } = securityQuestions.evalidatorType[0].SelectionList[0];
const { lga } = eValidationMasterData.masterData;
let lgaOptions = [];
lga.map(({ code: value, name: label }) => {
lgaOptions.push({ value, label });
});
return (
<View style={{minHeight: deviceHeight, color:'#fff'}}>
{loading &&
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<OverlaySpinner color="#00678F" />
</View>}
{!isCamera && (
<View style={styles.container}>
{formFields.map((document, index) => {
return (
<View style={{backgroundColor: '#fff' }}
key={index}>
{document.type === 'text' && (
<View style={{ padding: 15}} >
<View style={{ flexDirection: 'row', backgroundColor: '#fff' }}>
<SmallText textColor="grey" text={document.label} />
</View>
<Item style={{ borderColor: '#00fff', borderBottomWidth: 1, marginLeft: 0 }}>
<Input
value={lastName}
keyboardType='default'
onChangeText={(lastName) =>
this.setState({ lastName: lastName.replace(/\s/g, '') })}
/>
</Item>
</View>
)
}
{document.type === 'date' && (
<View style={{ padding: 15}}>
<View style={{
flexDirection: 'row', backgroundColor: '#fff'
}}>
<SmallText textColor="grey" text={document.label} />
</View>
<Item style={{ borderColor: '#00fff', borderBottomWidth: 1, marginLeft: 0 }}>
<DatePicker
minDate={minDate}
maxDate={currentDate}
currentDate={dateOfBirth}
format={format}
width={deviceWidth}
onChange={(dateOfBirth) => this.onChangeExp(dateOfBirth)}
marginLeft={0}
/>
</Item>
</View>
)}
</View>
);
}
)}
</View>
)}
<View style={{ flexDirection: 'column', padding: 15 }}>
<View style={{ flexDirection: 'column', flex:1 }}>
<SelectField options={lgaOptions} value={lgaValue}
onChange={this.lgaChanged} that={this}
label={"LGA of Origin"} />
</View>
<View style={{ flexDirection: 'column' }}>
<View style={{ flexDirection: 'row', backgroundColor: '#fff' }}>
<SmallText textColor="grey" text={"State of Origin"} />
</View>
<Item style={{ borderColor: '#00fff', borderBottomWidth: 1, marginLeft: 0 }}>
<Input
value={stateValue}
keyboardType='default'
onChangeText={(stateValue) =>
this.setState({ stateValue: stateValue.replace(/\s/g, '') })}
/>
</Item>
</View>
<View style={{ paddingBottom: 15, marginTop: 20 }}>
<PrimaryBtn label={'Validate User'} disabled={false}
onPress={() => this.validatUser()} />
</View>
</View>
</View>
);
}
}
export default CustomerEvalidation;
Please check my SupplierForm example hope you will get a lots of information
Constant data
"formFields": [{
"id": "1",
"name": "companyname",
"label": "Company name",
"type": "input",
"placeholder": "",
"returnKeyType": "go"
},
{
"id": "2",
"name": "supplier_type",
"label": "Supplier type",
"type": "drop_down"
},
{
"id": "3",
"name": "country",
"label": "Country",
"type": "drop_down"
},
{
"id": "4",
"name": "phonenumber",
"label": "Mobile number",
"type": "input",
"placeholder": "",
"returnKeyType": "next"
},
{
"id": "5",
"name": "phonenumber1",
"label": "Landline number",
"type": "input",
"placeholder": "",
"returnKeyType": "next"
},
{
"id": "6",
"name": "email",
"label": "Email / UserID",
"type": "input",
"placeholder": "",
"returnKeyType": "next"
},
{
"id": "7",
"name": "contact_person",
"label": "Contact person",
"type": "input",
"placeholder": "",
"returnKeyType": "next"
},
{
"id": "8",
"name": "skype_id",
"label": "Skype id",
"type": "input",
"placeholder": "",
"returnKeyType": "next"
},
{
"id": "9",
"name": "notes",
"label": "Notes",
"type": "input",
"placeholder": "",
"returnKeyType": "go"
}
]
SupplierForm component
// #flow
import * as React from 'react';
import { Div, Button, InputText, DropDown } from '../../materialComponents';
import data from '../../constantData/supplier';
/* flow types
============================================================================= */
type Props = {
action: string,
editData?: ?{
companyname: string,
supplier_type_id: number | string,
country: number | string,
phonenumber: string,
phonenumber1: string,
landlord_email: string,
contact_person: string,
landlord_skype_id: string,
notes: string,
},
data: Object,
buttonLoader: boolean,
onSubmit: (data: Object) => any,
onError: (type: string, msg: string) => any,
};
type State = {
companyname: string,
supplier_type_id: number | string,
country_id: number | string,
phonenumber: string,
phonenumber1: string,
email: string,
contact_person: string,
skype_id: string,
notes: string,
};
/* ============================================================================
<SupplierForm />
============================================================================= */
class SupplierForm extends React.Component<Props, State> {
static defaultProps = { editData: null };
constructor() {
super();
this.state = {
companyname: '',
supplier_type_id: 0,
country_id: 0,
phonenumber: '',
phonenumber1: '',
email: '',
contact_person: '',
skype_id: '',
notes: '',
};
}
componentDidMount() {
const { editData, action } = this.props;
if (action === 'edit') {
const record = { ...editData };
const {
companyname,
supplier_type_id,
country,
phonenumber,
phonenumber1,
landlord_email,
contact_person,
landlord_skype_id,
notes,
} = record;
this.setState({
companyname,
supplier_type_id,
country_id: country,
phonenumber,
phonenumber1,
email: landlord_email,
contact_person,
skype_id: landlord_skype_id,
notes,
});
}
}
// for submit form
_handleSubmit = () => {
const { onError, onSubmit } = this.props;
const {
companyname,
supplier_type_id,
country_id,
phonenumber,
phonenumber1,
email,
contact_person,
skype_id,
notes,
} = this.state;
const supplier = {
companyname,
supplier_type_id,
country_id,
phonenumber,
phonenumber1,
email,
contact_person,
skype_id,
notes,
};
if (!companyname) {
onError('error', 'Please Enter Company Name');
} else if (!Number(supplier_type_id)) {
onError('error', 'Please Select Supplier Type');
} else if (!Number(supplier_type_id)) {
onError('error', 'Please Select Country');
} else if (!email) {
onError('error', 'Please Enter Email');
} else {
onSubmit(supplier);
}
};
/**
* handle picker values
* #param {string} fieldName
* #param {string} value
*/
_handlePickerValue = (pickerName: string, _id: string) => {
this.setState({
[`${pickerName}_id`]: _id,
});
};
/**
* handle input values
* #param {string} fieldName
* #param {string} value
*/
_handleInputValue = (fieldName: string, value: string) => {
this.setState({ [fieldName]: value });
};
/**
* render the all input fields
* #param {Object} dropDownData
*/
_renderInputFields = (dropDownData: Object) => {
const { formFields } = data;
return formFields.map(field => {
if (field.type === 'input') {
return (
<InputText
key={field.id}
label={field.label}
value={this.state[field.name]}
onChangeText={value => this._handleInputValue(field.name, value)}
reference={input => {
// $FlowFixMe
this[`${field.name}_ref`] = input;
}}
returnKeyType={field.returnKeyType}
// $FlowFixMe
onSubmitEditing={() => this[`${field.name}_ref`].focus()}
/>
);
}
if (field.type === 'drop_down') {
const itemData =
field.name === 'country'
? dropDownData.countries
: dropDownData[`${field.name}s`];
const selectedValue = this.state[`${field.name}_id`];
return (
<DropDown
key={field.id}
label={field.label}
data={itemData}
selectedValue={selectedValue}
onChange={value => this._handlePickerValue(field.name, value)}
/>
);
}
return null;
});
};
render() {
const { data: dropDownData, buttonLoader, action } = this.props;
const buttonTitle = action === 'add' ? 'Save' : 'Update';
return (
<Div>
{this._renderInputFields(dropDownData)}
<Button
loader={buttonLoader}
color="#FFFFFF"
backgroundColor="#21A24F"
marginVertical={10}
borderRadius={2}
iconName="save"
title={buttonTitle}
onPress={this._handleSubmit}
/>
</Div>
);
}
}
/* export
============================================================================= */
export default SupplierForm;
sorry, i'm a lazy coder .... i'll try to answer AbriefAP . . .
state is not the only option to hold variables, use/create object and push values there;
var obj1={};
_on_fld_change(document,v){
obj1[document.name]=v;
}
_on_submit(){
return fetch(URL,'POST',{obj1})
}
render(){
...
return(
..
<Comp onValueChange={(v)=>this._on_fld_change(document,v)}/>
)
}
Related
I am pretty new to JavaScript and was hoping someone could help me out with this one.
I have this page that shows all the scheduled exams and when you press on "Learn more" a modal opens up that should let you modify information about the exam itself. At the moment it shows the equipment that is selected when creating the exam plus the rest of the equipment available and you should be able to select/deselect in order to change if needed. The problem is that a different modal opens up for each exam to show the corresponding data only. All the exam information I've got shown through mapping to get to the inside arrays of the "exams" nested array, so I do not know how to initialize a const before the render when I need the modal to be open to get that specific exams' info. At the moment I am mapping the values of the selected equipment which does not let me change the selection like I should.
https://codesandbox.io/s/81xer5
import "./styles.css";
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import Card from "#mui/material/Card";
import CardActions from "#mui/material/CardActions";
import CardContent from "#mui/material/CardContent";
import Button from "#mui/material/Button";
import Typography from "#mui/material/Typography";
import Modal from "#mui/material/Modal";
import Chip from "#mui/material/Chip";
import OutlinedInput from "#mui/material/OutlinedInput";
import InputLabel from "#mui/material/InputLabel";
import MenuItem from "#mui/material/MenuItem";
import FormControl from "#mui/material/FormControl";
import Select from "#mui/material/Select";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
}
};
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
boxShadow: 24,
p: 4,
borderRadius: 1
};
export default function App() {
const [exams, setExams] = useState([
{
id: "18897a8c-bd5b-4fc0-86d1-74ee509d46ee",
name: "New Test",
date: null,
time: null,
date2: "2022-06-20",
time2: "15:30",
students: [
{
id: "749ce920-2462-457a-8af3-26ff9c00dda5",
username: "student1",
email: "student1#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "90289548-19bb-480b-81e3-c36340debbc7",
username: "student2",
email: "student2#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
username: "student3",
email: "student3#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
}
],
staff: [
{
id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
username: "staff",
email: "staff#gmail.com",
firstName: "Staff",
lastName: "Staffov",
roleName: "STAFF"
}
],
rooms: [
{
id: "a49f18cb-4fe8-4a2c-a665-4361c5401f31",
number: 100,
nrOfSeats: 20
},
{
id: "5c46e888-fce4-4c1b-a8ec-e04d32a5cf6c",
number: 400,
nrOfSeats: 10
}
],
equipment: [
{
id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
name: "Crane",
availability: true
}
]
},
{
id: "65b7ecd2-ba30-4369-9f13-9186dc5cc73c",
name: "Crane Exam",
date: null,
time: null,
date2: null,
time2: null,
students: [
{
id: "749ce920-2462-457a-8af3-26ff9c00dda5",
username: "student1",
email: "student1#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "90289548-19bb-480b-81e3-c36340debbc7",
username: "student2",
email: "student2#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
},
{
id: "dfe50fe5-ef9d-480e-aa6c-2f5c81bb22da",
username: "student3",
email: "student3#gmail.com",
firstName: "Student",
lastName: "Studentov",
roleName: "STUDENT"
}
],
staff: [
{
id: "a3b53ed0-63fc-4f77-a8dc-74915d6aefea",
username: "staff",
email: "staff#gmail.com",
firstName: "Staff",
lastName: "Staffov",
roleName: "STAFF"
}
],
rooms: [
{
id: "a49f18cb-4fe8-4a2c-a665-4361c5401f31",
number: 100,
nrOfSeats: 20
},
{
id: "5c46e888-fce4-4c1b-a8ec-e04d32a5cf6c",
number: 400,
nrOfSeats: 10
}
],
equipment: [
{
id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
name: "Crane",
availability: true
},
{
id: "be1da3c9-7192-459f-bdba-767e005eaac9",
name: "Killer Robot",
availability: true
}
]
}
]);
const [equipment, setEquipment] = useState([
{
id: "08506d1b-30ce-43d2-a0b8-74f87082e356",
name: "Crane",
availability: true
},
{
id: "7a1716c7-3398-4e3d-9523-7ba4a102a79b",
name: "Lift",
availability: true
},
{
id: "be1da3c9-7192-459f-bdba-767e005eaac9",
name: "Killer Robot",
availability: true
}
]);
const initialShowState = Object.fromEntries(
exams.map((data) => [data.id, false])
);
const [show, setShow] = React.useState(initialShowState);
const toggleShow = (id) =>
setShow((prev) => {
return { ...prev, [id]: !prev[id] };
});
console.log({ show });
const [value, setValue] = React.useState([]); //this is what the select chip uses by default
const handleChange = (e) => {
const {
target: { value }
} = e;
console.log(value);
setValue(
// On autofill we get a the stringified value.
typeof value === "string" ? value.split(",") : value
);
};
return (
<div className="App">
{exams.map((data, key) => {
return (
<div key={key} style={{ width: "300px", display: "inline-block" }}>
<Box
sx={{
minWidth: 300,
maxWidth: 300,
display: "inline-block",
paddingTop: "10px",
paddingLeft: "10px"
}}
>
<Card variant="outlined">
<React.Fragment>
<CardContent>
<Typography variant="h5" component="div">
{data.name}
</Typography>
</CardContent>
<CardActions>
<Button size="small" onClick={() => toggleShow(data.id)}>
Learn More
</Button>
</CardActions>
</React.Fragment>
</Card>
</Box>
<Modal open={show[data.id]} onClose={() => toggleShow(data.id)}>
<Box sx={style}>
<Typography
component={"span"}
id="transition-modal-description"
sx={{ mt: 2 }}
>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
multiple
value={data.equipment.map((sub) => sub.id)}
// value={value}
onChange={handleChange}
input={
<OutlinedInput id="select-multiple-chip" label="Chip" />
}
renderValue={(selected) => {
return (
<Box
sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}
>
{selected.map((value) => {
const option = equipment.find(
(o) => o.id === value
);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
MenuProps={MenuProps}
>
{equipment.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
</FormControl>
</Typography>
</Box>
</Modal>
</div>
);
})}
</div>
);
}
Continuing my comment above, you're adding a <Modal> inside the map function, this will mount a <Modal> element for each exam, which is bad for performance and more difficult to implement.
What you want to do is to have only one modal, and upon clicking "Learn More" you save the active exam in a state, the modal uses this state to show the correct data. You also want to split the logic between the exam and the modal to make it more easy to implement.
Here is a sample code, I have moved the arrays outside the component to make the code more clear:
const EXAMS = [...];
const EQUIPMENTS = [...];
export default function App() {
const [exams, setExams] = useState(EXAMS);
const [equipment, setEquipment] = useState(EQUIPMENTS);
const [modalExam, setModalExam] = useState(null);
return (
<div className="App">
{exams.map((data, key) => {
return (
<div key={key} style={{ width: "300px", display: "inline-block" }}>
<Box
sx={{
minWidth: 300,
maxWidth: 300,
display: "inline-block",
paddingTop: "10px",
paddingLeft: "10px",
}}
>
<Card variant="outlined">
<React.Fragment>
<CardContent>
<Typography variant="h5" component="div">
{data.name}
</Typography>
</CardContent>
<CardActions>
<Button size="small" onClick={() => setModalExam(data)}>
Learn More
</Button>
</CardActions>
</React.Fragment>
</Card>
</Box>
</div>
);
})}
<ModalExam
equipment={equipment}
exam={modalExam}
onClose={() => setModalExam(null)}
/>
</div>
);
}
function ModalExam({ exam, equipment, onClose }) {
const [chipValue, setChipValue] = useState([]);
useEffect(() => {
if (exam) {
setChipValue(exam.equipment.map((sub) => sub.id));
}
}, [exam]);
const handleChange = (e) => {
const {
target: { value },
} = e;
console.log(value);
setChipValue(typeof value === "string" ? value.split(",") : value);
};
return (
<Modal open={exam !== null} onClose={onClose}>
{exam && (
<Box sx={style}>
<Typography
component={"span"}
id="transition-modal-description"
sx={{ mt: 2 }}
>
<p>{exam.name}</p>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="demo-multiple-chip-label">Chip</InputLabel>
<Select
multiple
value={chipValue}
// value={value}
onChange={handleChange}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) => {
return (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
{selected.map((value) => {
const option = equipment.find((o) => o.id === value);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
MenuProps={MenuProps}
>
{equipment.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
</FormControl>
</Typography>
</Box>
)}
</Modal>
);
}
See how much simple it gets when you split the logic. Here is the sandbox: https://codesandbox.io/s/hedk9g
I made a function getCategoryNameById to get and match the id of the product and I pass the function as props in the other component to use it but when it's render a else statement which is a "none" string.
Example Data
Product Dummy Data
{
id: 4,
name: "ByProgrammers Sushi",
rating: 4.9,
categories: [1],
priceRating: expensive,
photo: images.japanese_restaurant,
duration: "10 - 15 min",
location: {
latitude: 1.5578068150528928,
longitude: 110.35482523764315,
},
courier: {
avatar: images.avatar_4,
name: "Ahmad"
},
menu: [
{
menuId: 9,
name: "Sushi sets",
photo: images.sushi,
description: "Fresh salmon, sushi rice, fresh juicy avocado",
calories: 100,
price: 50
}
]
},
Dummy Category Data
const categoryData = [
{
id: 1,
name: "Rice",
icon: icons.rice_bowl,
},
The State
const [categories, setCategories] = useState(categoryData)
const [selectedCategory, setSelectedCategory] = useState(null)
const [restaurants, setRestaurants] = useState(restaurantData)
const [currentLocation, setCurrentLocation] = useState(initialCurrentLocation)
The Function
function getCategoryNameById(id) {
let category = categories.filter(a => a.id == id)
if(category.length > 0)
return category[0].name
return "none"
}
Home Component
return (
<HomeComponentContext.Provider value={{
selectedCategory
}}>
<SafeAreaView>
<Top name={currentLocation.streetName}/>
<Categories
item={categories}
funct={onSelectedCategory}
selectcat={selectedCategory}
/>
<Product
products={restaurants}
function={getCategoryNameById}
cate = {categories}
/>
</SafeAreaView>
</HomeComponentContext.Provider>
)
the Usage of the Function in the Product Component
{
props.cate.map((categoryId) => {
return(
<View
key={categoryId}
style={{
flexDirection:'row'
}}
>
<Text style={{ ...FONTS.body3 }}>
{props.function(categoryId)}
</Text>
<Text>.</Text>
</View>
)
})
}
I am a novice at redux and i am trying to incorporate it in my code so the state can be managed more easily. I am trying to map through an array in my state but it is throwing the error below:
Uncaught TypeError: Cannot read property 'map' of undefined
this is the reducer for redux
const initialState = {
workoutlist: [
{
id: uuid.v4(),
name: 'Leg Day',
date: '08-09-2019',
duration: "60",
exerciselist: [
{
id: uuid.v4(),
exerciseName: 'Squats',
numberOfSets: "3",
reps: "12",
weight: "135",
},
{
id: uuid.v4(),
exerciseName: 'Leg press',
numberOfSets: "3",
reps: "10",
weight: "150",
},
{
id: uuid.v4(),
exerciseName: 'Lunges',
numberOfSets: "4",
reps: "12",
weight: "0",
},
],
selected: false,
},
{
id: uuid.v4(),
name: 'Running',
date: '08-11-2019',
duration: "40",
exerciselist: [],
selected: false,
},
],
disabled: true,
page: 1,
}
const workoutList = (state = initialState, action) => {
switch(action.type) {
// Returns whether the panel is selected or not
// and enables Start Workout button if only 1 is selected
case 'SELECT_PANEL':
state = {
workoutlist: state.workoutlist.map(workout => {
if (workout.id === action.id) {
workout.selected = !workout.selected
}
return workout;
})
}
var count = 0;
state.workoutlist.map((workout) => {
if (workout.selected === true) {
count = count + 1;
}
return count;
})
if (count !== 1) {
state = {
...state,
disabled: true
}
} else {
state = {
...state,
disabled: false
}
}
return state;
default:
return state;
}
}
this is the component that where the error is being thrown.
export default function WorkoutItem() {
const handleSelectedPanel = useSelector(state => state.workoutList);
const dispatch = useDispatch();
const { name, date, duration, exerciselist } = handleSelectedPanel;
return (
<ExpansionPanel style={styles.panel} onChange={() => dispatch(selectPanel())}>
<ExpansionPanelSummary>
<Typography variant="button" style={styles.header}>
{name}
</Typography>
<Typography variant="button" style={styles.header}>
{date}
</Typography>
<Typography align="right" style={styles.header}>
~{duration} mins
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Table size="medium" style={styles.table}>
<TableHead>
<TableRow>
<TableCell padding="none" >Name</TableCell>
<TableCell padding="none" align="right"># of sets</TableCell>
<TableCell padding="none" align="right">average reps</TableCell>
<TableCell padding="none" align="right">weight</TableCell>
</TableRow>
</TableHead>
<TableBody>
{exerciselist.map((exercise) => (
<ExerciseList
key={exercise.id}
exercise={exercise}
/>
))}
</TableBody>
</Table>
<ExpansionPanelActions disableSpacing style={styles.actionButton}>
<EditWorkoutItem
workout={this.props.workout}
handleEditChange={this.props.handleEditChange}
/>
</ExpansionPanelActions>
</ExpansionPanelDetails>
</ExpansionPanel>
)
}
It is supposed to show a list of panels that can be expanded to show the contents but it throws an error saying the exerciselist is undefined in my state apparently.
Any help would be greatly appreciated!
Do you want to access the first item or workoutList ?. From your code handleSelectedPanel seams to be an array not an object. Is your destructuring correct?. Where is your useSelector?. What does console.log(handleSelectedPanel) gives you? Try
const { name, date, duration, exerciselist } = handleSelectedPanel[0];
or
const handleSelectedPanel = useSelector(state => state.workoutList[0]);
Here I am trying to fetch data from API post method and trying to populate on listview , and after selecting particular data I'll go to net page.
By get method I have done that.
But when I am running this code blank list is coming .
How can I parse value from groupData.name? After getting data on list I can select particular data and send to other screen ,but for now , how can I populate data in list?
import React, { Component } from 'react';
import { View, Text, TextInput,
FooterTab,Button,TouchableOpacity, ScrollView,ListView, StyleSheet,
ActivityIndicator ,Header,icon} from 'react-native';
import { createStackNavigator } from 'react-navigation';
import { SearchBar } from 'react-native-elements';
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
class RenderList extends Component {
static navigationOptions = {
title: 'Selected Item',
header: null,
};
constructor() {
super();
this.state = {
data: null,
loading: true,
search: '',
};
}
componentDidMount() {
this.createViewGroup();
}
createViewGroup = async () => {
try {
const response = await fetch(
'http:///Dsenze/userapi/grouphier/viewgroup',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
password: 'admin',
username: 'admin',
viewall: 'false',
id: [4],
startlimit: '0',
valuelimit: '10',
}),
}
);
const responseJson = await response.json();
const { groupData } = responseJson;
this.setState({
data: groupData,
loading: false,
});
} catch (e) {
console.error(e);
}
};
clickedItemText(clickedItem) {
this.props.navigation.navigate('Item', { item: clickedItem });
}
updateSearch = search => {
this.setState({ search });
};
keyExtractor = ({ id }) => id.toString();
renderItem = ({ item }) => (
<TouchableOpacity
style={styles.item}
activeOpacity={0.4}
onPress={() => {
this.clickedItemText(item);
}}>
<Text style={styles.text}>Hospital name {name}</Text>
</TouchableOpacity>
);
render() {
const { loading, data } = this.state;
return (
<ScrollView>
<View style={styles.container1}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
data={data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>
)}
</View>
</ScrollView>
);
}
}
class ClickedItem extends Component
{
constructor() {
super();
this.state = {
inputValue: '',
// Default Value of the TextInput
// Default value for the QR Code
};
}
static navigationOptions =
{
title: "Selected Item",
header: null
};
render()
{
return(
<ScrollView>
<View style = { styles.container2 }>
<TextInput style={styles.inputBox}
underlineColorAndroid='rgba(0,0,0,0)'
placeholder="Hospital Id"
editable={false}
placeholderTextColor="#000000"
onChangeText={(hospital_id) => this.setState({hospital_id})}>{ this.props.navigation.state.params.item.id }</TextInput>
<TextInput style={styles.inputBox}
underlineColorAndroid='rgba(0,0,0,0)'
placeholder="Field 2"
secureTextEntry={false}
placeholderTextColor="#000000"
onChangeText={(Field2) => this.setState({Field2})}/>
<TouchableOpacity style={styles.button}onPress={() => {Insert(this.state.hospital_id,this.state.Field2,this.state.Field3,this.state.Field4,this.state.Field5,this.state.Field6);{this.getTextInputValue}}}>
<Text style={styles.buttonText}>Insert</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
}
}
export default InvDemoPost = createStackNavigator(
{
List: { screen: RenderList,
header: true},
Item: { screen: ClickedItem,
header: null }
});
const styles = StyleSheet.create(
{
container1:
{
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
container2:
{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 15
},
inputBox:{
width:300,
borderColor: '#48BBEC',
backgroundColor: '#F8F8FF',
borderRadius:25,
paddingHorizontal:16,
fontSize:16,
color:'#000000',
marginVertical:10,
},
button:{
width:300,
backgroundColor:'#4169E1',
borderRadius:25,
marginVertical:10,
paddingVertical:16
},
buttonText:{
fontSize:16,
fontWeight:'500',
color:'#ffffff',
textAlign:'center'
},
item:
{
padding: 15
},
text:
{
fontSize: 18
},
separator:
{
height: 2,
backgroundColor: 'rgba(0,0,0,0.5)'
}
});
Below is the Json data ,that I have to populate on the list view
{
"groupData": [{
"hierarchy": 4,
"id": 4,
"name": "St.Mary's Hospitals",
"parent": 3,
"type": 2
}, {
"hierarchy": 4,
"id": 5,
"name": "Mandya Clinic",
"parent": 6,
"type": 2
}, {
"hierarchy": 4,
"id": 7,
"name": "Blr Clinic",
"parent": 3,
"type": 2
}, {
"hierarchy": 4,
"id": 8,
"name": "kings hospital",
"parent": 3,
"type": 2
}, {
"hierarchy": 4,
"id": 9,
"name": "jason hospital",
"parent": 3,
"type": 1
}],
"success": "true"
}
I am using the following output:
{
"groupData": [{
"hierarchy": 4,
"id": 4,
"name": "St.Mary's Hospitals",
"parent": 3,
"type": 2
}, {
"hierarchy": 4,
"id": 5,
"name": "Mandya Clinic",
"parent": 6,
"type": 2
}, {
"hierarchy": 4,
"id": 7,
"name": "Blr Clinic",
"parent": 3,
"type": 2
}, {
"hierarchy": 4,
"id": 8,
"name": "kings hospital",
"parent": 3,
"type": 2
}, {
"hierarchy": 4,
"id": 9,
"name": "jason hospital",
"parent": 3,
"type": 1
}],
"success": "true"
}
Assuming this object is assigned to this.state.dataSource
<FlatList
data={this.state.dataSource.groupData}
renderItem={({item: { name }) => <Text>{name}</Text>}
keyExtractor={({id}) => id.toString()}
/>
But, you must remember that you're fetching data in componentDidMount, which is called after the first render. This means that this.state.dataSource will be empty until your API call has succeeded.
You could add an additional state variable isLoading. Then inside your render function you could have some logic to render a Spinner or a List, depending on the status of your API call.
render() {
const { isLoading, dataSource: { groupData } } = this.state;
if(isLoading) {
return <Text>Loading</Text>
}
return (
<FlatList
data={groupData}
renderItem={({item: { name }) => <Text>{name}</Text>}
keyExtractor={({id}) => id.toString()}
/>
)
}
Then inside of the logic of your API call, where you set dataSource, you can then set isLoading to false.
Updated to reflect code in question
class RenderList extends Component {
static navigationOptions = {
title: 'Selected Item',
header: null,
};
constructor() {
super();
this.state = {
data: null,
loading: true,
search: '',
};
}
componentDidMount() {
this.createViewGroup();
}
createViewGroup = async () => {
try {
const response = await fetch(
'http:///Dsenze/userapi/grouphier/viewgroup',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
password: 'admin',
username: 'admin',
viewall: 'false',
id: [4],
startlimit: '0',
valuelimit: '10',
}),
}
);
const responseJson = await response.json();
const { groupData } = responseJson;
this.setState({
data: groupData,
loading: false,
});
} catch (e) {
console.error(e);
}
};
clickedItemText(clickedItem) {
this.props.navigation.navigate('Item', { item: clickedItem });
}
updateSearch = search => {
this.setState({ search });
};
keyExtractor = ({ id }) => id.toString();
renderItem = ({ item }) => (
<TouchableOpacity
style={styles.item}
activeOpacity={0.4}
onPress={() => {
this.clickedItemText(item);
}}>
<Text style={styles.text}>Hospital name {name}</Text>
</TouchableOpacity>
);
render() {
const { loading, data } = this.state;
return (
<ScrollView>
<View style={styles.container1}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
data={data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>
)}
</View>
</ScrollView>
);
}
}
I had a very long form which around 20 different fields and I displayed those input area using map function. I want to valid the input data when I click the submit button and jump to the corresponding input required box.
const ReportFields = [
{
title: "Report Title*",
field: "report_title",
type: "text",
required: true
},
{
title: "Submitting Agency*",
field: "submitting_agency",
type: "text",
required: true
},
{
title: "Division*",
field: "division",
type: "select",
required: true
},
{
title: "Committee*",
field: "committee",
type: "select",
required: true
},
{
title: "Assigned Contact*",
field: "assigned_contact",
type: "select",
required: true
},
{
title: "Other Recipients",
field: "other_recipients",
type: "text",
required: false
}];
class App extends Component {
state = {
report: {
report_title: "",
submitting_agency: "",
division: "",
committee: "",
assigned_contact: "",
other_recipients: ""
},
errorMessage: "",
refs: {}
}
componentDidMount() {
this.registerRefs();
}
registerRefs = () => {
const refs = ReportFields.reduce((acc, current) => {
const ref = React.createRef();
acc[current.field] = ref;
return acc;
}, {});
this.setState({ refs });
}
onSubmit = (e) => {
e.preventDefault();
for (let i = 0; i < ReportFields.length; i++) {
const curt = ReportFields[i];
if (curt.required && this.state.report[curt.field] === "") {
this.setState({errorMessage: `${curt.title} cannot be empty!`});
this.state.refs[curt.field].current.focus();
break;
}
}
}
render() {
const display = ReportFields.map((field, idx) => {
return (
<div key={idx}>
<p>{field.title}</p>
<input
type={field.type}
onChange={(e) => {
this.setState({
report: {...this.state.report, [field.field]: e.target.value}
})
}}
ref={this.state.refs[field.field]}
></input>
</div>
);
})
return (
<div className="App">
{display}
<input type="button" value="submit" onClick={this.onSubmit}/>
</div>
);
}
}
export default App;
I tried to use react refs but it doesn't work, any idea?
Also, I am actually using these content in react modal, will this be one of the reason why it doesn't work?
Ok here is a solution I know who can work but I don't say it's the best one. A working example here https://codesandbox.io/s/94v4r6w7kr. As you can see when you click submit you jump to password input.
How do that work ? First as you can see we need a way to save all the ref we gonna create. I save it in the state refs here. The way that work is a loop over each field and for each one I createRef and add this to an object. I use this object inside the state. When you want to use it after that, you then can call this.state.refs[thenameoftheinput].current.focus().
This is an example, and I let you make it work with your own data. But I hope that can give you an idea :)
const ReportFields = [
{
title: "Report Title*",
field: "report_title",
type: "text",
required: true
},
{
title: "Submitting Agency*",
field: "submitting_agency",
type: "text",
required: true
},
{
title: "Division*",
field: "division",
type: "select",
required: true
},
{
title: "Committee*",
field: "committee",
type: "select",
required: true
},
{
title: "Assigned Contact*",
field: "assigned_contact",
type: "select",
required: true
},
{
title: "Other Recipients",
field: "other_recipients",
type: "text",
required: false
}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
refs: {}
};
}
componentDidMount() {
this.registerRefs();
}
registerRefs = () => {
const refs = ReportFields.reduce((acc, current) => {
const ref = React.createRef();
acc[current.field] = ref;
return acc;
}, {});
this.setState({ refs });
};
focusTextInput = () => {
this.state.refs.division.current.focus();
};
render() {
const inputs = ReportFields.map(el => {
return <input placeholder={el.title} ref={this.state.refs[el.field]} />;
});
return (
<div>
<form>
{inputs}
<input type="button" value="submit" onClick={this.focusTextInput} />
</form>
</div>
);
}
}
Iterate through all fields and create a separate ref for each one of them. Use a unique identifier (as a suggestion - the name property) to access the ref later.
class App extends Component {
constructor(props) {
super(props);
this.focusTextInput = this.focusTextInput.bind(this);
// Fields
this.ReportFields = [
{
type: "text",
name: "firstname",
title: "First Name"
},
{
type: "text",
name: "lastname",
title: "Last Name"
}
];
this.inputRefs = this.ReportFields.reduce((acc, field) => ({
...acc,
[field.name]: React.createRef()
}), {});
}
state = {
a: {
b: "",
c: "",
d: "",
e: "",
f: "",
g: "",
h: "",
i: "",
j: "",
k: "",
l: "",
m: "",
n: "",
o: "",
p: "",
q: "",
r: "",
s: "",
t: "",
u: "",
v: "",
w: "",
x: "",
y: ""
},
errorMessage: ""
};
focusTextInput() {
// Focus on the input you wish, in this case "firstname"
console.log(this.inputRefs["firstname"].current.focus());
}
render() {
const display = this.ReportFields.map((field, idx) => {
return (
<div key={idx}>
<p>{field.title}</p>
<input
type={field.type}
onChange={e => {
this.setState({
report: { ...this.state.report, [field.field]: e.target.value }
});
}}
ref={this.inputRefs[field.name]}
/>
</div>
);
});
return (
<div className="App">
{display}
<input type="button" value="submit" onClick={this.focusTextInput} />
</div>
);
}
}