React Native - Handling dynamic form depending on it's parent answer - javascript

I have a simple state using JSObject, and the state look like:
pertanyaan: [{
label: "label1",
type: 'dropdown',
key: 'keyFoo1',
option: [{
value: "foo1"
},
{
value: "foo2",
additional: {
label: 'label1-1',
type: 'date',
key: 'keyFoo1-1',
option:null
}
},
{
value: "foo3",
additional:{
label: 'label1-2',
type: 'dropdown',
key: 'keyFoo1-2',
option:[
{value:"Tidak ada orang"},
{value:"Bertemu tetangga"},
]
}
},
]
},
{
label: "Label2",
type: 'dropdown',
key: 'keyFoo2',
option: [{
value: "Rumah"
},
{
value: "Tempat Kerja"
},
]
}
]
With those JSObject, i want to achieve some form depending on the answer of each parent,
Example: label1 has 3 option ( foo1, foo2, foo3), if the answer of label1 is foo2 i need to render Date Component, and if label1 answering foo3 i need to render Dropdown Component,
with below code I just can rendering label1 with foo2 answer:
renderVisit(){
var renderin = this.state.pertanyaan.map((item, index)=>{
if(this.state[item.key] == undefined){
this.setState({[item.key]:item.option[0].value})
}
let data = item.option.filter((val)=>{return val.value == this.state[item.key]})[0]
return(
<View>
{/*dropdown Component*/}
<View key={index}>
<CustomText>{item.label}</CustomText>
<Picker
mode="dropdown"
selectedValue={this.state[item.key]}
onValueChange={(itemVal)=> this.onChangePicker(item, index, itemVal)}
>
{item.option.map((itemPicker, indexPicker)=>{
return(
<Picker.Item label={itemPicker.value} value={itemPicker.value} key={indexPicker} color={Color.blue_900}/>
)
})}
</Picker>
</View>
{data!=undefined && data.additional!=undefined &&
{/*dateComponent*/}
<View>
<CustomText>{data.additional.label}</CustomText>
<TouchableOpacity onPress={()=>this.openDate(data.additional)}>
<CustomText>{this.state[data.additional.key] == undefined? "Select Date" : new Date(this.state[data.additional.key]).toDateString()}</CustomText>
<MaterialCommunityIcons name="calendar" size={34} />
</TouchableOpacity>
</View>
}
</View>
)
})
return renderin
}
anyone can help me to achieve my goal and makes the code more readable?

This is the way how I would implement the dynamic selection of a dropdown component. You can use the DropDown component recursively by passing the component itself as a child of another dropdown;
const Date = () => 'Date Component';
const Foo = () => 'Foo';
const Bar = () => 'Bar';
class ListItem extends React.Component {
handleClick = () => {
const { option: {id}, onClick } = this.props;
onClick(id);
}
render(){
const { option: { label } } = this.props;
return (
<li onClick={this.handleClick}>{label}</li>
)
}
}
class DropDown extends React.Component {
state = {
selectedOption: null
}
handleOptionClick = optionId => {
const { options } = this.props;
this.setState({ selectedOption: options.find(option => option.id === optionId).child });
}
render(){
const { selectedOption } = this.state;
const { options } = this.props;
return (
<ul>
{options.map(option => (
<ListItem
option={option}
onClick={this.handleOptionClick}
/>
))}
{selectedOption}
</ul>
)
}
}
const DropDownOptions = [
{id: '1', label: 'label-1', child: <Date />},
{id: '2', label: 'label-2', child: <DropDown options={[
{id: '2-1', label: 'label-2-1', child: <Foo />},
{id: '2-2', label: 'label-2-2', child: <Bar />}
]} />}
]
ReactDOM.render(<DropDown options={DropDownOptions} />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

REACT-SELECT defaultValue in CustomDropdown not working

I want the default value of my dropdown to be defaultValue={item.taste} (value from match.json) but it's not working... (go to /menu/Menu1 and Pea Soup)
import Select from "react-select";
export default function CustomDropdown({ style, options, defaultValue }) {
return (
<div style={style}>
<Select options={options} defaultValue={defaultValue} />
</div>
);
}
MenuItemDisplay:
export default function MenuItemDisplay() {
const { menuId, itemId } = useParams();
const { match } = JsonData;
const matchData = match.find((el) => el._id_menu === menuId)?._ids ?? [];
const item = matchData.find((el) => el._id === itemId);
const styles = {
select: {
width: "100%",
maxWidth: 150
}
};
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
...
return (
<>
<div className="TextStyle">
{"Taste "}
<CustomDropdown
style={styles.select}
options={TASTE}
defaultValue={item.taste}
//The default value is not working only if it's
//TASTE[0]
/>
</div>
...
</>
);
}
Here the link for the code
As defaultValue you need to pass one of the objects of the TASTE array. You can do this:
<CustomDropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find(t => t.label === item.taste)}
/>

How do I update deeply nested array with hooks in react?

I have a nested array of objects, each object have a nested options array like this.
const [formFields, setFormFields ] = useState({
formTitle: '',
fields: [
{name: 'country', val: '', type: 'radio', options: ['Japan', 'Korea', 'usa'] },
{name: 'state', val: '', type: 'select', options: ['texas', 'florida']},
{name: 'location', val: '', type: 'text', options: []},
]})
Each of the items in the nested options array is supposed to be a value in a textInput which is editable.
I want to be able to add/remove/edit these values inside the textInput with a button click.
Please how will I be able to achieve this?
my code
<Containter>
{formFields.fields.map((field, index) => (
<View key={index}>
<View>
<TextInput
onChangeText={(value ) => {
onChange({name: field.name, value });
}}
value={field.name}
/>
</View>
{(field.type === 'select' || field.type === 'radio') && (
<>
{field.options.map((option) => (
<TextInput value={option}
onChangeText={(value ) => {
onChange({name: field.options, ...field.options, value });
}}
/>
<Text onPress={removeOption}>X</Text>
))}
<Button title="add option" />
</>
)
}
<IconButton
icon="delete"
onPress={handleRemoveField}
/>
</View>
))}
<Button
onPress={handleAddField}
title="Add"
/>
</Containter>
Add & remove implementation:
onAdd (index,value) {
const fields = formFields.fields.map((field,i) => {
if (i==index) {
const options = [...field.options,value]
return {...field, options}
}
return field
})
setFormFields(
{
...formFields,
fields
}
)
}
onRemove (index,value) {
const fields = formFields.fields.map((field,i) => {
if (i==index) {
const options = field.options.filter((item) => item != value)
return {...field, options}
}
return field
})
setFormFields(
{
...formFields,
fields
}
)
}
// in constructor
this.onChange = this.onChange.bind(this)
// in class
onChange (index,value) {
this.setState(state => {
const fields = state.fields.map((field,i) => {
if (i==index) field.val = value
return field
})
return {
...state,
fields
}
})
}
// in component
onChangeText( (e) => onChange(index, e.target.value) )
For value changing:
onChange (index,value) {
const fields = formFields.fields.map((field,i) => {
if (i==index) field.val = value
return field
})
setFormFields({
...formFields,
fields
})
}
...
// somewhere in input element
<TextInput ... onChangeText={(e) => onChange(index,e.target.value)} .. />

React/Javascript passing props spread syntax vs. specific syntax

I am coding a recursive component.
If I pass props like this: <RecursiveComponent itemProps={data} />, it won't work. I have to pass like this: <RecursiveComponent {...data} />. Why is that?
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import * as React from 'react'
const data = {
name: 'Level 1',
children: [
{
name: 'Level 2',
children: [
{
name: 'Level 3'
}
]
},
{
name: 'Level 2.2',
children: [
{
name: 'Level 3.2'
}
]
}
]
}
const RecursiveComponent = (itemsProp) => {
const hasData = itemsProp.children && itemsProp.children.length;
return (
<>
{itemsProp.name}
{hasData && itemsProp.children.map((child) => {
console.log(child);
return <RecursiveComponent {...child} key={child.name} />
})}
</>
)
}
function App() {
return (
<div className="App">
<RecursiveComponent {...data} />
</div>
);
}
export default App;
React components take an object as first argument: the props. When you use jsx, the attributes you pass are converted to an object.
<Component name="test" id={42} ok={true} /> // props = { name: "test", id: 42, ok: true }
Then your component receive the props like this:
const Component = (props) => {
console.log(props.name) // => "test"
// the rest of your component...
}
// Most people will use destructuration to use directly the attribute name
const Component = ({ name, id, ok }) => {
console.log(name) // => "test"
// the rest of your component...
}
The spread operator allow you to fill attributes for every properties of an object
const data = { name: "test", id: 42, ok: true }
<Component {...data} />
// is the same as :
<Component name={data.name} id={data.id} ok={data.ok} />
In your case when you pass itemsProp={data} you actually have the first argument of your component like this
const RecursiveComponent = (itemsProp) => {
// here itemsProp = { itemsProp : { name : "Level 1", children : [...] }}
// so you have to use it this way
const hasData = itemsProp.itemsProp.children && itemsProp.itemsProp.children.length;
}
you can use it with passing props <RecursiveComponent itemProps={data} /> also, see below , notice the {} brace on props while getting props in function component.
Different way 1:-
const RecursiveComponent = (props) => {
const hasData = props.itemProps.children && props.itemProps.children.length
}
you can use like above as well.
Solution 2:-
import React from "react";
const data = {
name: "Level 1",
children: [
{
name: "Level 2",
children: [
{
name: "Level 3"
}
]
},
{
name: "Level 2.2",
children: [
{
name: "Level 3.2"
}
]
}
]
};
const RecursiveComponent = ({itemProps}) => {
console.log("itemsprops", itemProps);
const hasData = itemProps.children && itemProps.children.length;
return (
<>
{itemProps.name}
{hasData && itemProps.children.map((child) => {
console.log(child);
return <RecursiveComponent itemProps={child} key={child.name} />
})}
</>
);
};
function App() {
return (
<div className="App">
<RecursiveComponent itemProps={data} />
</div>
);
}
export default App;

Raising and Handling events in React

I want to increment the value on Items component on a button click which is handled by its child component Item. Items have an array of key-value pairs and this value needs to increment and render it
Here is the code
//parent component
class Items extends React.Component{
state={
items:[{ id: 1, value: 9 },
{ id: 2, value: 10 },
{ id: 3, value: 0 }]
}
handleIncrement=()=>{
//need to increment items.value on each button click increment. How can I access it
}
render(){
return(
<div>
<h2>Increment item on the list From Parent</h2>
{this.state.items.map(item=>(<Item key={item.id}
value={item.value} id={item.id} onIncrement={this.handleIncrement}
/>))}
</div>
)
}
}
//child component
class Item extends React.Component{
getValue=()=>{
let {value}=this.props;
return value===0?'Zero':value
}
render(){
return(
<div>
<span>{this.getValue()}</span>
<button onClick={this.props.onIncrement}>Increment</button>
</div>
)
}
}
please help me with this.
You might add id as button name
<button name={this.props.id} onClick={this.props.onIncrement}>Increment</button>
and then use it at your function
handleIncrement= e =>
this.setState({ items: this.state.items.map(item =>
item.id == e.target.name ? {...item, value: item.value++ } : item ) })
Or you can update by array index instead of object id
//parent component
class Items extends React.Component {
state = {
items: [{ id: 1, value: 9 }, { id: 2, value: 10 }, { id: 3, value: 0 }]
};
handleIncrement = e => {
//need to increment items.value on each button click increment.How can i access it
const id = e.target.id;
const tempItems = this.state.items;
tempItems[id] = {...tempItems[id], value: ++tempItems[id].value}
this.setState((prevState)=>({ items: tempItems}));
};
render() {
return (
<div>
<h2>Increment item on the list From Parent</h2>
{this.state.items.map((item,i) => (
<Item
key={item.id}
value={item.value}
id={item.id}
index={i}
onIncrement={this.handleIncrement}
/>
))}
</div>
);
}
}
//child component
class Item extends React.Component {
getValue = () => {
let { value } = this.props;
return value === 0 ? "Zero" : value;
};
render() {
return (
<div>
<span>{this.getValue()}</span>
<button id={this.props.index} onClick={this.props.onIncrement}>Increment</button>
</div>
);
}
}
state = {
items: [{ id: 1, value: 9 }, { id: 2, value: 10 }, { id: 3, value: 0 }]
};
handleIncrement = id => event => {
event.preventDefault();
const s = JSON.parse(JSON.stringify(this.state.items)); // dereference
const ndx = s.map(e => e.id).indexOf(id);
s[ndx]["value"]++;
this.setState({ items: s });
};
here's a sandbox you can preview with the implementation:
https://codesandbox.io/s/wonderful-voice-kkq7b?file=/src/Increment.js:380-803

How to change React Dropdown's title?

I'm creating a custom dropdown list, where a button (Trigger) plays the role as the dropdown's trigger. Here I'm trying to change the dropdown title into the name of any selected options. To do this, I store the new selected value in selectedOption and use them to replace the title. However receive an error of: Cannot read property 'label' of undefined.
How to resolve and make the dropdown works?
Really appreciate any enlightenment! Thank you
const Dropdown = props => {
const { onChange, label, disabled } = props;
const options = [
{ value: '0', label: 'All Flavour' },
{ value: '1', label: 'Strawberry' },
{ value: '2', label: 'Rum Raisin' },
{ value: '3', label: 'Hazelnut' },
{ value: '4', label: 'Chocochip' },
{ value: '5', label: 'Coffee' },
];
const [open, setOpen] = useState(false);
const handleTriggerClick = useCallback(() => setOpen(prev => !prev), []);
const handleChange = useCallback(
newValue => {
if (!disabled) {
onChange(newValue);
setOpen(false);
}
},
[onChange]
);
const selectedOption = options.find(option => option.label === label);
const displayMenu = open && !disabled;
return (
<>
<Container>
<OutletIcon />
<Trigger
disabled={disabled}
title={selectedOption.label || ''}
onClick={handleTriggerClick}
>
<TriggerText>{selectedOption.label || ''}</TriggerText>
<SortIcon />
</Trigger>
<DropdownMenu isDisplayed={displayMenu}>
{options.map(option => {
const isSelected = option.label === label;
const otherProps = {};
if (!isSelected) {
otherProps.onClick = () => handleChange(option.label);
}
return (
<DropdownMenuItem
key={option.value}
title={option.label}
selected={isSelected}
{...otherProps}
>
<DropdownMenuItemText onClick={handleTriggerClick}>
{option.label}
</DropdownMenuItemText>
<GreenCheckIcon />
</DropdownMenuItem>
);
})}
</DropdownMenu>
</Container>
</>
);
};
Hereby is the props declaration
Dropdown.defaultProps = {
disabled: false,
onChange: () => {},
label: '',
};
Dropdown.propTypes = {
disabled: PropTypes.bool,
onChange: PropTypes.func,
label: PropTypes.string,
};

Categories