While I am trying to add a new Subject to the specific Year and Quarter it is getting dragged into, my list is updating on screen, I am getting all of the element in the list, but when I try to check for things like duplicates inside the list (to not allow them), the list appears to be empty. I might suspect that it has something to do with the rerendering of the component without "fetching" the context again, but to be fair I have no clue what to try next to solve this.
import { Box, Typography } from "#mui/material";
import { useDrop } from "react-dnd";
import SubjectCard from "./SubjectCard";
import { Subject } from "../models/Subject";
import { useContext, useEffect } from "react";
import {
CurricullumContext,
CurricullumContextType,
} from "../context/CurricullumContext";
interface Props {
year: number;
quarter: number;
}
function QuarterComponent({ year, quarter }: Props) {
const yearProp = "year" + year;
const quarterProp = "quarter" + quarter;
const { curricullum, dispatch } = useContext(
CurricullumContext
) as CurricullumContextType;
const subjects = curricullum[yearProp]![quarterProp]!.subjects;
useEffect(() => {
console.log("subjects useEffect", subjects);
}, [curricullum]);
const [{ isOver }, drop] = useDrop(() => ({
accept: "subject",
drop: (item: Subject) => {
console.log("dropped", item);
console.log("subjects in drop", subjects);
addSubjectToYear(item);
},
collect: (monitor) => ({
isOver: !!monitor.isOver({ shallow: true }),
}),
}));
const addSubjectToYear = (subject: Subject) => {
console.log("subjects: ", curricullum[yearProp]![quarterProp]!.subjects);
if (!subjects.some((s: any) => s.courseName === subject.courseName)) {
dispatch({
type: "ADD_SUBJECT_TO_QUARTER",
payload: {
year: yearProp,
quarter: quarterProp,
subject: subject,
},
});
}
};
return (
<Box
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
ml={2}
>
<Typography variant="h5">Quartile {quarter}</Typography>
<Box
display="flex"
flexDirection="column"
width={200}
height={400}
border={isOver ? "2px solid red" : "2px solid black"}
ref={drop}
bgcolor={isOver ? "lightsalmon" : "white"}
>
{subjects.length > 0 &&
subjects.map((subject: Subject) => <SubjectCard subject={subject} />)}
{subjects.length === 0 && (
<Typography variant="h6">Drop subjects here</Typography>
)}
</Box>
</Box>
);
}
export default QuarterComponent;
I tried adding a console.log using useEffect to try and capture the list at every render and on each drop of a new item i get around 16 console.logs, but the interesting part is that always the first log shows the list with all of the items that it should have, onlyt after the first one all of the rest are empty arrays.
QuarterComponent.tsx:28 subjects useEffect (2) [{…}, {…}]0: {id: 1, language: 'English', credits: 5, courseName: 'OOP', version: '1', …}1: {id: 2, language: 'English', credits: 5, courseName: 'Databases', version: '2', …}length: 2[[Prototype]]: Array(0)
QuarterComponent.tsx:28 subjects useEffect []
QuarterComponent.tsx:28 subjects useEffect []
Related
I have a dynamic form where users can add multiple products. I wonder how I could save the selected products' id.
In the console.log(fields, "fields");, this is where I can see the saved product. So how can I save the selected product id as well?
Any help would be appreciated. Thank you.
Codesandbox: https://codesandbox.io/s/react-hook-form-wizard-form-from-reddit-with-data-ouy64e?file=/src/fieldArray.js:322-4143
const products = [
{
prodName: "Tumbler",
price: 1.5,
size: "500",
colorMap: { Black: 20, Pink: 10, Green: 5 },
id: "aRLMZkiSU7T0lcsPCSsV"
},
{
prodName: "Shirt",
price: 2.0,
size: "L",
colorMap: { Blue: 10, Black: 10 },
id: "uTHIR6OQFRuqP9Drft0e"
},
{
size: "200",
price: 2.0,
colorMap: { Green: 50, Red: 19, Black: 20 },
prodName: "Notebook",
id: "y9ECyZBKp2OBekmWym4M"
}
];
const options = products.map(
(object) =>
object.prodName +
" - " +
object.size +
`${object.cat === "CM" || object.cat === "ML" ? "- " + object.cat : ""}` +
" "
);
console.log(options, "options");
const FieldArray = ({ control, register, setValue, getValues }) => {
const { fields, append, remove, prepends } = useFieldArray({
control,
name: "order"
});
console.log(fields, "fields");
renderCount++;
return (
<div>
<ul>
{fields.map((item, index) => {
console.log(item);
return (
<li key={item.id}>
<Controller
control={control}
name={`order.${index}.product`}
render={({ field: { onChange, value = "", ...rest } }) => (
<Autocomplete
{...rest}
onInputChange={(e, newValue) => {
onChange(newValue);
console.log(newValue, "new value");
}}
inputValue={value}
options={products}
// isOptionEqualToValue={(option, value) =>
// option?.value === value?.value
// }
getOptionLabel={(option) =>
option.prodName + " " + option.size
}
// getOptionLabel={(option) => option?.label ?? ""}
renderInput={(params) => (
<TextField
{...params}
label="Product"
variant="outlined"
fullWidth
/>
)}
/>
)}
/>
);
})}
</div>
);
};
export default FieldArray;
Update
this is the submit button in step1.js
const onSubmit = (data) => {
// action(data);
console.log(data, "d");
const newOrder = [];
data.order.forEach(({ product, variation }) => {
const newVariantion = [];
variation.forEach(({ qty, color }) => {
newVariantion.push({ qty: parseInt(qty), color });
});
newOrder.push({ product, variation: newVariantion });
});
actions.updateAction(data);
console.log(newOrder, "new order");
navigate("/step2", newOrder);
};
Update:
How would I be able to push the product ID inside the newOrder where it matches the productID of the selected product?
Some development on answer from this question:
You can always add useState with a first product (save entire product, not just an id) and then manage everything through onChange:
import {useState} from 'react';
/*...something here...*/
const FieldArray = ({ control, register, setValue, getValues }) => {
const [prod, setProd] = useState({0: product[0]});
/*...something here...*/
{fields.map((item, index) => {
/*...something here...*/
<Autocomplete
onChange={(e, v)=>{console.log(v); setProd({...prod, [index]:v});}}
value={prod[index] || {}}
options={products}
/*...other stuff here...*/
Have a look at what is available in console.log(v) inside onChange.
Also check out difference between inputValue and value here.
Update
If you need multiple products to be saved - prod must be an object with key to represent "fields" item. For example, something like this {0: prod1, 1: prod3, 2: prod11}. Then for value use prod[index] and change setter appropriately. (I've edited code above for this case). There is multiple ways to do this - that's just one from top of my head.
Update 2:
I don't know what you want in onSubmit exactly, so here is an idea and you change it to what you want.
In Step1.onSubmit you can do something like that:
// forEach 2nd argument is an index (counter)
data.order.forEach(({ product, variation }, indx) => {
// some code
newOrder.push({ product, variation: newVariantion, prod: prod[indx] });
// more code
}
Right now I'm building a DnD game.
It looks like this:
When I drop an answer over a slot, the answer should be in that exact place, so the order matters.
My approach kind of works, but not perfectly.
So, in the parent component I have two arrays which store the slots' content and the answers' content.
The problem is easy to understand, you don't have to look at the code in-depth, just mostly follow my text :)
import DraggableGameAnswers from './DraggableGameAnswers';
import DraggableGameSlots from './DraggableGameSlots';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useState } from 'react';
type DraggableGameStartProps = {
gameImages: Array<string>,
gameAnswers: Array<string>,
numberOfGameAnswers?: number,
typeOfAnswers: string,
correctAnswer: string
}
function DraggableGameStart(props: DraggableGameStartProps) {
const [slots, setSlots] = useState<string[]>(["", "", "", ""]);
const [answers, setAnswers] = useState<string[]>(props.gameAnswers);
return (
<DndProvider backend={HTML5Backend}>
<div className="draggable-game-content">
<div className="draggable-game">
<DraggableGameSlots
answers={answers}
slots={slots}
setAnswers={setAnswers}
setSlots={setSlots}
numberOfAnswers={4}
slotFor={props.typeOfAnswers}
/>
<DraggableGameAnswers
gameAnswers={answers}
numberOfGameAnswers={props.numberOfGameAnswers}
typeOfAnswers={props.typeOfAnswers}
/>
</div>
</div>
</DndProvider>
);
}
export default DraggableGameStart;
DraggableGameSlots is then a container, which loops through props.slots and renders each slot.
import React, { useState } from "react";
import DraggableGameSlot from "./DraggableGameSlot";
type DraggableGameSlotsProps = {
answers: string[],
slots: string[],
setAnswers: any,
setSlots: any,
numberOfAnswers: number,
slotFor: string
}
function DraggableGameSlots(props: DraggableGameSlotsProps) {
return (
<div className="draggable-game-slots">
{
props.slots.map((val, index) => (
<DraggableGameSlot
index={index}
typeOf={props.slotFor === "text" ? "text" : "image"}
key={index}
answers={props.answers}
slots={props.slots}
setAnswers={props.setAnswers}
setSlots={props.setSlots}
toDisplay={val === "" ? "Drop here" : val}
/>
))
}
</div>
);
}
export default DraggableGameSlots;
In DraggableGameSlot I make my slots a drop target. When I drop an element, I iterate through the slots, and when I reach that element's position in the array, I modify it with the answer.
Until now everything works fine, as expected, I just wrote this for context.
E.g. if the drop target's index is 2, I modify the 3rd position in the slots array. (slots2 for 0-index based)
import { useEffect } from 'react';
import { useDrop } from 'react-dnd';
import './css/DraggableGameSlot.css';
import DraggableGameImage from './DraggableGameImage';
type DraggableGameSlotProps = {
index: number,
typeOf: string,
answers: string[],
slots: string[],
setAnswers: any,
setSlots: any,
toDisplay: string
}
function DraggableGameSlot(props: DraggableGameSlotProps) {
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item: { id: string, toDisplay: string }) {
props.setAnswers(props.answers.filter((val, index) => index !== parseInt(item.id)));
props.setSlots(props.slots.map((val, index) => {
if (props.index === index) {
console.log("ITEM " + item.toDisplay);
return item.toDisplay;
}
return val;
}));
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
})
}), [props.slots])
// useEffect(() => console.log("answers " +props.answers), [props.answers]);
// useEffect(() => console.log("slots " + props.slots), [props.slots]);
const dragClass: string = isOver ? "is-dragged-on" : "";
return (
<>
{(props.typeOf === "image" && props.toDisplay !== "Drop here") ?
<>
<span>da</span>
<DraggableGameImage
className={`game-answers-image ${dragClass}`}
imgSrc={props.toDisplay}
imgAlt={"test"}
dndRef={drop}
/>
</>
:
<div className={`draggable-game-slot draggable-game-slot-for-${props.typeOf} ${dragClass}`} ref={drop}>
<span>{props.toDisplay}</span>
</div>
}
</>
)
}
export default DraggableGameSlot;
The problem comes with the logic in DraggableGameAnswer.
First, the container, DraggableGameAnswer. Using the loop, I pass that "type" (it is important because from there the error happens)
DraggableGameAnswers.tsx
import './css/DraggableGameAnswers.css';
import GameNext from '../GameComponents/GameNext';
import DraggableGameAnswer from './DraggableGameAnswer';
import { useEffect } from 'react';
type DraggableGameAnswersProps = {
gameAnswers: Array<string>,
numberOfGameAnswers?: number,
typeOfAnswers: string
}
function DraggableGameAnswers(props: DraggableGameAnswersProps) {
let toDisplay: Array<string>;
if(props.numberOfGameAnswers !== undefined)
{
toDisplay = props.gameAnswers.slice(props.numberOfGameAnswers);
}
else
{
toDisplay = props.gameAnswers;
}
useEffect(() => console.log(toDisplay));
return (
<div className="draggable-game-answers">
{
toDisplay.map((val, index) => (
<DraggableGameAnswer
key={index}
answer={val}
typeOfAnswer={props.typeOfAnswers}
type={`answer${index}`}
/>
))}
<GameNext className="draggable-game-verify" buttonText="Verify"/>
</div>
);
}
export default DraggableGameAnswers;
In DraggableGameAnswer, I render the draggable answer:
import DraggableGameImage from "./DraggableGameImage";
import "./css/DraggableGameAnswer.css";
import { useDrag } from "react-dnd";
import { ItemTypes } from "./Constants";
type DraggableGameAnswerProps = {
answer: string,
typeOfAnswer: string,
type: string
}
function DraggableGameAnswer(props: DraggableGameAnswerProps) {
const [{ isDragging }, drag] = useDrag(() => ({
type: "image",
item: { id: ItemTypes[props.type],
toDisplay: props.answer,
typeOfAnswer: props.typeOfAnswer },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
})
}))
return (
<>
{props.typeOfAnswer === "image" ?
<DraggableGameImage
className="draggable-game-image-answer"
imgSrc={props.answer}
imgAlt="test"
dndRef={drag}
/> :
<div className="draggable-game-text-answer" ref={drag}>
{props.answer}
</div>
}
</>
);
}
export default DraggableGameAnswer;
Constants.tsx (for ItemTypes)
export const ItemTypes: Record<string, any> = {
answer0: '0',
answer1: '1',
answer2: '2',
answer3: '3'
}
Okay, now let me explain what's happening.
The error starts because of the logic in DraggableGameAnswers container - I set the type using the index for the loop.
Everything works fine at the beginning. Then, let's say, I move answer3 in in the 4th box. It looks like this now:
It is working fine until now.
But now, the components have re-rendered, the map runs again, but the array is shorter with 1 and answer1's type will be answer1, answer2's type will be answer2, and answer4's type will be answer3.
So, when I drag answer4 over any box, I will get another answer3, like this:
How can I avoid this behavior and still get answer4 in this situation? I'm looking for a react-style way, better than my idea that I'll describe below.
So, here is how I delete an answer from the answers array:
props.setAnswers(props.answers.filter((val, index) => index !== parseInt(item.id)));
I can just set the deleted's answer position to NULL, to keep my array length for that index, and when I render the answers if the value is NULL, just skip it.
This is the code I am trying to rebuild using functional component, but my arrays do not behave correctly.
EXPECTED RESULT: https://stackblitz.com/edit/antd-showhidecolumns
My forked functional component version:
MY WORK https://stackblitz.com/edit/antd-showhidecolumns-rdyc8h
Main issue here is I am not able to show/hide column cells, I am not sure why my array is different when I use the same method as the original code.
My code:
const onChange = (e) => {
let { checkedColumns } = colmenu;
if (e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (!e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
const filtered = checkedColumns.filter((el) => {
return el.dataIndex !== checkedColumns.el;
});
console.log('filtered items', filtered);
setColmenu({ ...colmenu, columns: filtered });
};
working version from the old code (class component)
onChange = (e) => {
var checkedColumns = this.state.checkedColumns
if(e.target.checked){
checkedColumns = checkedColumns.filter(id => {return id !== e.target.id})
}
else if(!e.target.checked){
checkedColumns.push(e.target.id)
}
var filtered = this.state.initialColumns;
for(var i =0;i< checkedColumns.length; i++)
filtered = filtered.filter(el => {return el.dataIndex !== checkedColumns[i]})
this.setState({columns: filtered, checkedColumns: checkedColumns})
}
Something really went wrong with your code (or homework i guess?)
Please have a look at least at the docs for React.useState to set some basics.
First you should init your initalColumns and later you should filter on them.
Additional i init the checkColumns with the correct values and changed the wrong logic for changing them.
Have a look how the filtering is done via Array.includes maybe someone will ask for this ;-)
Another point is that you may split the state object in separate primitive states.
Nevertheless here is a working stackblitz and the depending code.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Table, Button, Dropdown, Menu, Checkbox } from 'antd';
const App = () => {
const columns = [
{
title: 'Description',
dataIndex: 'description',
},
{
title: 'Employees',
dataIndex: 'employees',
},
];
const [colmenu, setColmenu] = React.useState({
value: false,
checkedColumns: ['description', 'employees'],
visibleMenuSettings: false,
columns,
initialColumns: columns,
});
const onChange = (e) => {
let { checkedColumns, columns, initialColumns } = colmenu;
if (!e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
console.log(columns);
columns = initialColumns.filter((col) =>
checkedColumns.includes(col.dataIndex)
);
setColmenu({ ...colmenu, columns, checkedColumns });
};
const handleVisibleChange = (flag) => {
setColmenu({ ...colmenu, visibleMenuSettings: flag });
};
const menu = (
<Menu>
<Menu.ItemGroup title="Columns">
<Menu.Item key="0">
<Checkbox id="description" onChange={onChange} defaultChecked>
Description
</Checkbox>
</Menu.Item>
<Menu.Item key="1">
<Checkbox id="employees" onChange={onChange} defaultChecked>
Employees
</Checkbox>
</Menu.Item>
</Menu.ItemGroup>
</Menu>
);
const dataSource = [
{
key: '1',
description: 'Holiday 1',
employees: '79',
},
{
key: '2',
description: 'Holiday 2',
employees: '12',
},
{
key: '3',
description: 'Holiday 3',
employees: '0',
},
];
return (
<div>
<div className="row">
<div className="col-12 mb-3 d-flex justify-content-end align-items-center">
<Dropdown
overlay={menu}
onVisibleChange={handleVisibleChange}
visible={colmenu.visibleMenuSettings}
>
<Button>Show/Hide Columns</Button>
</Dropdown>
</div>
</div>
<div className="row">
<div className="col-12">
<Table
columns={colmenu.columns}
dataSource={dataSource}
size="small"
pagination={{
pageSizeOptions: ['20', '50'],
showSizeChanger: true,
}}
/>
</div>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('container'));
I have several productOptionTypes with dynamic values. My problem is how do I match it with its variants. I want to get the variant id based on it.
Pls check codesandbox here CLICK HERE
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
Best I could come up with is this:
Update the option mapping to return the option name, to be used as the foreign key into the variants array objects, i.e. "Color", "Size", etc.
options={item.optionValues.map((option) => {
return {
label: option,
value: option,
name: item.optionName
};
})}
Update the variantType state to be an object, and update the handleSelectVariant handler to store the variant types by the foreign key "name" and the selected "value"
const [variantType, setSelectedVariant] = useState({});
const handleSelectVariant = (value, index) => {
setSelectedVariant((state) => ({
...state,
[value.name]: value.value
}));
};
Use a filter and every function to reduce the option types and values to a filtered result of variants that can be easily mapped to the id properties.
const matches = variants
.filter((variant) => {
return [
["option1Value", "option1Type"],
["option2Value", "option2Type"],
["option3Value", "option3Type"],
["option4Value", "option4Type"],
["option5Value", "option5Type"],
["option6Value", "option6Type"],
["option7Value", "option7Type"],
["option8Value", "option8Type"],
["option9Value", "option9Type"],
["option10Value", "option10Type"]
].every(([key, valueKey]) =>
variantType[variant[valueKey]]
? variant[key] === variantType[variant[valueKey]]
: true
);
})
.map(({ id }) => id);
Demo
When i understand it correctly you want to safe every selected value, then compare the result to your variants and select the variantId of the item matching all selctboxes based on it?
index.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import "./styles.css";
import { productOptionTypes } from "./productOptionTypes";
import { variants } from "./variants";
function App() {
const [variantType, setSelectedVariant] = useState({});
const [result, setResult] = useState(null);
const mapIndexKey = {
0: "option1Value",
1: "option2Value",
2: "option3Value",
3: "option4Value"
};
useEffect(() => {
setResult(
variants.filter(
(el) =>
el.option1Value == variantType.option1Value &&
el.option2Value == variantType.option2Value &&
el.option3Value == variantType.option3Value &&
el.option4Value == variantType.option4Value
)
);
}, [variantType]);
useEffect(() => {
console.log(result);
}, [result]);
const handleSelectVariant = (value, index) => {
setSelectedVariant({ ...variantType, [mapIndexKey[index]]: value.value });
console.log(variantType, value, index);
};
return (
<div className="App">
<form>
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
</form>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
variants.js
export const variants = [
{
id: "60451fd290aeb720d96d8459",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01524blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "XS",
option3Type: "Toe Type",
option3Value: "Half Toe",
option4Value: "Bellarina",
retailPrice: "16.0",
__typename: "Variant"
},
{
id: "60451fd790aeb720d96d8463",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01525blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "S",
option3Type: "Toe Type",
option3Value: "Half Toe",
retailPrice: "16.0",
__typename: "Variant"
}
}
Basicly what i do is saving all selected values in a map and everytime a value is changed just comparing it to all variants, if a variant is equal i select it.
For one variant i added the fourth key "Bellarina".
I hope this solution solves your problem.
When you want to verify the solution just select the first value of every selectbox and have a look at the console.
I am trying to use JsonSchema-Form component but i ran into a problem while trying to create a form that, after choosing one of the options in the first dropdown a secondary dropdown should appear and give him the user a different set o options to choose depending on what he chose in the first dropdown trough an API call.
The thing is, after reading the documentation and some examples found here and here respectively i still don't know exactly how reference whatever i chose in the first option to affect the second dropdown. Here is an example of what i have right now:
Jsons information that are supposed to be shown in the first and second dropdowns trough api calls:
Groups: [
{id: 1,
name: Group1}
{id: 2,
name: Group2}
]
User: [User1.1,User1.2,User2.1,User2.2,User3.1,User3.2, ....]
If the user selects group one then i must use the following api call to get the user types, which gets me the the USER json.
Component That calls JSonChemaForm
render(){
return(
<JsonSchemaForm
schema={someSchema(GroupOptions)}
formData={this.state.formData}
onChange={{}}
uiSchema={someUiSchema()}
onError={() => {}}
showErrorList={false}
noHtml5Validate
liveValidate
>
)
}
SchemaFile content:
export const someSchema = GroupOptions => ({
type: 'object',
required: [
'groups', 'users',
],
properties: {
groups: {
title: 'Group',
enum: GroupOptions.map(i=> i.id),
enumNames: GroupOptions.map(n => n.name),
},
users: {
title: 'Type',
enum: [],
enumNames: [],
},
},
});
export const someUISchema = () => ({
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
},
types: {
'ui:options': {
size: {
lg: 15,
},
},
},
});
I am not really sure how to proceed with this and hwo to use the Onchange method to do what i want.
I find a solution for your problem.There is a similar demo that can solve it in react-jsonschema-form-layout.
1. define the LayoutField,this is part of the demo in react-jsonschema-form-layout.To make it easier for you,I post the code here.
Create the layoutField.js.:
import React from 'react'
import ObjectField from 'react-jsonschema-form/lib/components/fields/ObjectField'
import { retrieveSchema } from 'react-jsonschema-form/lib/utils'
import { Col } from 'react-bootstrap'
export default class GridField extends ObjectField {
state = { firstName: 'hasldf' }
render() {
const {
uiSchema,
errorSchema,
idSchema,
required,
disabled,
readonly,
onBlur,
formData
} = this.props
const { definitions, fields, formContext } = this.props.registry
const { SchemaField, TitleField, DescriptionField } = fields
const schema = retrieveSchema(this.props.schema, definitions)
const title = (schema.title === undefined) ? '' : schema.title
const layout = uiSchema['ui:layout']
return (
<fieldset>
{title ? <TitleField
id={`${idSchema.$id}__title`}
title={title}
required={required}
formContext={formContext}/> : null}
{schema.description ?
<DescriptionField
id={`${idSchema.$id}__description`}
description={schema.description}
formContext={formContext}/> : null}
{
layout.map((row, index) => {
return (
<div className="row" key={index}>
{
Object.keys(row).map((name, index) => {
const { doShow, ...rowProps } = row[name]
let style = {}
if (doShow && !doShow({ formData })) {
style = { display: 'none' }
}
if (schema.properties[name]) {
return (
<Col {...rowProps} key={index} style={style}>
<SchemaField
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
uiSchema={uiSchema[name]}
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
registry={this.props.registry}
disabled={disabled}
readonly={readonly}/>
</Col>
)
} else {
const { render, ...rowProps } = row[name]
let UIComponent = () => null
if (render) {
UIComponent = render
}
return (
<Col {...rowProps} key={index} style={style}>
<UIComponent
name={name}
formData={formData}
errorSchema={errorSchema}
uiSchema={uiSchema}
schema={schema}
registry={this.props.registry}
/>
</Col>
)
}
})
}
</div>
)
})
}</fieldset>
)
}
}
in the file, you can define doShow property to define whether to show another component.
Next.Define the isFilled function in JsonChemaForm
const isFilled = (fieldName) => ({ formData }) => (formData[fieldName] && formData[fieldName].length) ? true : false
Third,after you choose the first dropdown ,the second dropdown will show up
import LayoutField from './layoutField.js'
const fields={
layout: LayoutField
}
const uiSchema={
"ui:field": 'layout',
'ui:layout': [
{
groups: {
'ui:autofocus': true,
'ui:options': {
size: {
lg: 15,
},
},
}
},
{
users: {
'ui:options': {
size: {
lg: 15,
},
},
doShow: isFilled('groups')
}
}
]
}
...
render() {
return (
<div>
<Form
schema={schema}
uiSchema={uiSchema}
fields={fields}
/>
</div>
)
}