Related
I'm implementing sorting via draggable with recoil and react-dnd.
It was confirmed that the index to be replaced and the target index came in normally, but when the onMove function was executed, the data in the questionItemIdList array was initialized to the initial value.
Thinking that the index value was set as id, it was initialized as it is now even after changing the logic with a unique id value instead of the index value.
When you check the console in the 3rd line of the onMove function above, the data array was initialized.
How can I solve this??
const MultipleSelection = ({ questionIndex, partIndex, sysCode, hasNextSectionFlag, ListLength }: IProps) => {
const [questionItemIdList, setQuestionItemIdList] = useRecoilState(qusetionItemIdListAtom(sysCode));
// console.log(questionList);
const onMove = useCallback((dragIndex: number, hoverIndex: number) => {
const dragInput = questionItemIdList[dragIndex];
console.log(questionItemIdList, dragIndex, hoverIndex);
setQuestionItemIdList(
update(questionItemIdList, {
$splice: [
[dragIndex, 1], // Delete
[hoverIndex, 0, dragInput], // Add
],
})
);
// console.log(questionItemIdList);
}, []);
const renderInput = useCallback((id: QuestionItemListID, index: number, arr: QuestionItemListID[]) => {
return (
<InputAndNextPartContainer
key={id}
hasNextSectionFlag={hasNextSectionFlag}
hasDeleteBtn={arr.length > 1}
partId={partIndex}
questionId={questionIndex}
selectionNumber={index + 1}
id={sysCode} //해당 선택지 리스트에 대한 id값
idName={id} //선택지 리스트 안에 있는 고유 id 값
ListLength={ListLength}
moveCard={onMove}
/>
);
}, []);
return (
<DndProvider backend={TouchBackend}>
<Option>{questionItemIdList.map((id, idx, arr) => renderInput(id, idx, arr))}</Option>
</DndProvider>
);
};
//InputAndNextPartContainer
const InputAndNextPartContainer = ({id, moveCard}: IProps) => {
const [showModalState, setshowModalState] = useState(false);
return (
<Draggable handleMove={moveCard} index={id} id={id}>
...
</Draggable>
);
};
const Draggable = ({ children, handleMove, index, id }: IProps) => {
const ref = useRef<HTMLDivElement>(null);
const debounceHoverItem = _.debounce((item: DragItem, monitor: DropTargetMonitor) => {
if (!ref.current) {
return;
}
if (item.index === undefined) return;
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) {
return;
}
const hoverBoundingRect = ref.current?.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
handleMove(dragIndex, hoverIndex);
item.index = hoverIndex;
}, 70);
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
accept: ItemTypes.QUESTION_ITEM,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
};
},
hover(item: DragItem, monitor: DropTargetMonitor) {
debounceHoverItem(item, monitor);
},
});
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.QUESTION_ITEM,
item: () => {
return { id, index };
},
collect: (monitor: any) => ({
isDragging: monitor.isDragging(),
}),
});
drag(drop(ref));
return (
<DraggableWrapper ref={ref} data-handler-id={handlerId} isdragging={isDragging ? 1 : 0}>
{children}
</DraggableWrapper>
);
};
Hello I made a custom hook that goes hand in hand with a component for generic forms, however, I notice that it is slow when the state changes.
#customHook
export const useFormController = (meta) => {
const { setVisible, setLoading } = useContext(FeedBackContext);
const itemsRef = useRef([]);
const {
control,
setValue,
handleSubmit,
formState: { errors },
} = useForm<Partial<any>>({
mode: "onBlur",
shouldUnregister: true,
resolver: yupResolver(meta.validation),
});
const onRef = function (input) {
this.itemsRef.current[this.index] = input;
};
const onSubmit = (data: any) => {
if(meta.onSubmit){
meta.onSubmit(data);
}else{
setVisible(true);
setLoading(true);
meta.service.submit(data);
}
};
const isJsonEmpty = (val = {}) => {
return Object.keys(val).length == 0;
};
const onSubmitIditing = function () {
let index = ++this.index;
if (isJsonEmpty(errors) && this.end) {
handleSubmit(onSubmit)();
} else if (!this.end) {
this.itemsRef.current[index]._root.focus();
}
};
const setFields = (json) => {
const items = Object.keys(json);
const values = Object.values(json)
console.log('Cambiando fields en formControllser...', json)
for (let i = 0; i < items.length; i++) {
//console.log('Cambiando valores...', items[i], values[i])
setValue(items[i], values[i], { shouldValidate: true })
}
}
const getItems = () => {
console.log('Meta namess', meta.names, meta);
if (!meta && !meta.names) return [];
return meta.names.map(function (item, index) {
const isEnd =
meta.options && meta.options[item] && meta.options[item].end
? true
: false;
const isSecure =
meta.options && meta.options[item] && meta.options[item].secure
? true
: false;
const label = meta.alias ? meta.alias[item] : item;
const visible = meta.invisibles ? (meta.invisibles[item] ? false : true) : true;
const def = meta.defaults ? meta.defaults[item] : "";
const disabled = (val) => {
const b = meta.disableds ? (meta.disableds[item] ? true : false) : false;
return b;
}
return {
name: item,
label: label,
disabled: disabled,
onRef: onRef.bind({ itemsRef: itemsRef, index: index }),
onSubmitEditing: onSubmitIditing.bind({
itemsRef: itemsRef,
index: index,
end: isEnd,
errors: errors,
}),
visible: visible,
setFields,
defaultValue: def,
errors: errors,
secureTextEntry: isSecure,
styles: styles,
control: control,
options: meta.options[item] ? meta.options[item] : null,
};
});
}
const getData = useMemo(() => {
console.log('Get data calback v2', meta);
return {
handleSubmit,
items: getItems(),
onSubmit,
errors,
setFields
};
}, [meta])
return getData;
};
export const Client: React.FC<any> = React.memo(({ navigation, route }) => {
const {
alias,
defaults,
ubigeoSeleccionado,
setUbigeoSeleccionado,
editable,
inputLabel,
search,
getDisabled,
getInvisibles,
getAlias,
getDefaults,
disableds,
invisibles,
searchVisible,
idTypeDocument,
currentTypeDocument,
allTypeDocuments,
onValueChange,
onChangeText } = useContext(CreateClientContext);
const [mode, setMode] = useState(() => {
return route?.params?.mode;
})
const [client, setClient] = useState(() => {
return route?.params?.client;
})
const { dispatchClient } = useContext(GlobalContext);
const clientService = useClientService();
const ref = useRef(0);
const options = useMemo(() => {
return {
names: ["ane_numdoc", "ane_razsoc", "ane_alias", "ane_email", "ane_tel", "ane_tel2", "ane_dir"],
validation: clientValidation,
alias: alias,
defaults: defaults,
disableds: disableds,
service: {
submit: (data) => {
const parse = { ...data, ubigeo_id: ubigeoSeleccionado.ubi_id, ane_tipo_cp: 2, ane_tipdoc: currentTypeDocument.id }
if (mode == "update") {
//console.log('Actualizando...', client.id, parse);
clientService.updateById(client.id, parse)
.then(ok => {
Alert.alert('Actualizaciòn de cliente', "Cliente Actualizado")
dispatchClient({
type: 'create',
payload: ok
});
setTimeout(() => {
navigation.navigate('App', {
screen: "Clients"
})
}, 500)
}).catch(e => {
Alert.alert('Actualizaciòn de cliente', "No se pudo actualizar")
})
} else {
clientService.create(parse)
.then(ok => {
dispatchClient({
type: 'create',
payload: ok
});
Alert.alert('Cliente', "Cliente creado")
setTimeout(() => {
navigation.navigate('App', {
screen: "Clients"
})
}, 500)
})
.catch(e => {
(e);
Alert.alert('Error', "No se pudo crear el cliente")
})
}
}
},
invisibles: invisibles,
options: {
ane_dir: {
end: true
},
}
}
}, [getDisabled,
getInvisibles,
getAlias,
getDefaults])
const { items, handleSubmit, onSubmit, errors, setFields } = useFormController(options);
useEffect(() => {
ref.current++;
})
useEffect(() => {
if (route.params) {
console.log('Ref current', ref.current);
setMode(route.params.mode);
setClient(route.params.client);
}
}, [route.params])
useEffect(() => {
// console.log('Mode', mode, client.id);
if (mode == "update" && client) {
console.log('cambiando fields'), ref;
setFields(client)
}
}, [mode, client])
// useEffect(()=>{
// },[instanceDocument])
useEffect(() => {
console.log('Cambiando cliente...', mode, client);
console.log(ref.current);
}, [client])
useEffect(() => {
//Creación
console.log('set defaults..', ref.current);
if (Object.keys(defaults).length > 0) {
setFields(defaults)
}
}, [getDefaults])
console.log('Current', ref.current);
return (
<StyleProvider style={getTheme(material)}>
<Container style={{ backgroundColor: "#FAF9FE" }}>
<Content style={GlobalStyles.mainContainer}>
<Text style={GlobalStyles.subTitle}>Cliente</Text>
<PickerSearch
search={search}
editable={editable}
style={styles}
searchVisible={searchVisible}
placeholder={inputLabel}
pickerItems={allTypeDocuments}
onValueChange={onValueChange}
selectedValue={idTypeDocument}
onChangeText={onChangeText}
></PickerSearch>
<FormListController
// top={<Top />}
items={items}
style={GlobalStyles}
></FormListController>
<Bottom
ubigeoSeleccionado={ubigeoSeleccionado}
setUbigeoSeleccionado={setUbigeoSeleccionado}
onSubmit={handleSubmit(onSubmit)}
/>
</Content>
<AppFooter2 navigation={navigation} />
</Container>
</StyleProvider>
);
});
export const FormListController: React.FC<any> = React.memo(({ children, items = [], style, top = null, bottom = null }) => {
console.log('%c Form list controlllser...', "background-color:#ccc");
console.log('items', items)
return (
<>
<Form style={!!style.form ? style.form : style.formContainer}>
{top}
{items.map((item: any, index) => {
return <FormItemController {...item} key={index} />;
})}
{bottom}
</Form>
</>
);
});
export const FormItemController: React.FC<any> = React.memo((props: any) => {
console.log('Form item controller print', props)
if (props.visible) {
return (
<>
<Controller
control={props.control}
render={
({ field: { onChange, onBlur, value } }) => {
return (
<Item regular style={props.styles.item}>
<Label style={props.styles.label}>{props.label}</Label>
<Input
onBlur={onBlur}
disabled={props.disabled(value)}
onChangeText={(value) => onChange(value)}
secureTextEntry={props.secureTextEntry}
onSubmitEditing={props.onSubmitEditing}
value={value}
ref={props.onRef}
/>
</Item>
)
}
}
defaultValue={props.defaultValue}
name={props.name}
/>
{props.errors && props.errors[props.name] && (
<TextError value={props.errors[props.name].message} />
)}
{/* {props.options && props.options.errorEmpty && props.errors[""] && (
<TextError value={props.errors[""].message} />
)} */}
</>
);
}
else {
return <></>
}
});
I use the same component to create and edit a client, but when editing and viewing the FormItemController the logs time span is less than 1 second, however it is not rendered until after 8 or 10 seconds.
This is the output of my logs.
Update cliente... 500ms
set defaults.. 77
Client num render 77
Client num render 78
Client num render 79
Client num render 80
Client num render 81
Client num render 82
Client num render 83
Client num render 84
Client num render 85
Client num render 86
Client num render 87
Client num render 88
Client num render 89
Client num render 90
Client num render 91
Client num render 92
Client num render 93
Client num render 94
Client num render 95
Client num render 96
Client num render 97
Client num render 98
Client num render 99
Client num render 100 (6-8 seg)
the problem I have is when I edit, when I use the forms to create I have no problems, I do not find the bottleneck to improve and prevent it from being slow.
After trying several options, I realized that when passing the setValue, I was sending several nulls and objects that did not go with the form, filtering this data, made the final rendering pass from 8 seconds to less than 1 second
const setFields = (json) => {
const items = Object.keys(json);
const values = Object.values(json)
for (let i = 0; i < items.length; i++) {
if (!!values[i] && typeof values[i] != 'object') {
setValue(items[i], values[i])
}
}
}
For this project I am currently working on, I need to highlight the button that was clicked on each layer/row. However, the way I have right now it highlights every button that was clicked.
I need something like this:
correct highlighted path
But then when I click on the same row, it does not remove the highlight from the button that I pressed before. How can I update and reset the state of the previous button that was clicked? I tried to use the useRef hook for this but I haven't been successful so far.
wrong highlighted path
EDIT: Added code
This is the code that I have for the component of each row in the website:
function StackRow({ data, partition, level, index, onClick, getInfo, isApp }) {
const classes = useStyles({ level: level });
const rowRef = useRef();
const handleSelectedButtons = (flag, setFlag, btnRef) => {
console.log(rowRef);
};
return (
<Card
key={partition + '_' + index + '_' + level}
className={classes.card}
id={level}
ref={rowRef}
>
{data.map((field) => {
return (
<StackItem
key={partition + '_' + field[0] + '_' + level}
data={field[0]}
info={field[1]}
level={level}
onClick={onClick}
getInfo={getInfo}
isApp={isApp}
handleSelectedButtons={handleSelectedButtons}
rowRef={rowRef}
/>
);
})}
</Card>
);
}
And this is the code I have for each button of the row:
function StackItem({
data,
info,
level,
onClick,
getInfo,
isApp,
handleSelectedButtons,
}) {
const [flag, setFlag] = useState(false);
const btnRef = useRef();
const styleProps = {
backgroundColor: flag ? '#06d6a0' : level % 2 === 0 ? '#22223b' : '#335c67',
};
const classes = useStyles(styleProps);
return (
<Button
ref={btnRef}
isselected={flag.toString()}
key={data}
className={classes.button}
variant="outlined"
onClick={(event) => {
onClick(event, setFlag, btnRef);
handleSelectedButtons(flag, setFlag, btnRef);
getInfo(info, level, isApp);
}}
disableElevation={true}
>
{data}
</Button>
);
}
There are some useless variables and states there because I have been trying all sort of stuff to do this.
EDIT: Added data sample & project structure
Data looks like:
{
application: {
cmake: {
info: str,
versions: {
version_no: {
application: {...}
}
}
},
gcc: {...},
git: {...},
intel: {...},
.
.
.
}
}
The structure of the project is like:
App
L Stack
L StackRow
L StackItem
Where App is the entire application, Stack is the container for everything in the images apart from the search box, StackRow matches one row of the Stack, and StackItem is one item/button from the StackRow.
EDIT: Added Stack component
function Stack({ data, partition, getInfo }) {
const [level, setLevel] = useState(0);
const [cards, setCards] = useState([]);
const [isApp, setIsApp] = useState(true);
const [selected, setSelected] = useState([]);
const [prevLevel, setPrevLevel] = useState(-1);
const cardsRef = useRef();
const handleClick = (event, setFlag, btnRef) => {
let rows = cardsRef.current.childNodes;
let currBtn = event.target.innerText;
let curr;
for (let i = 0; i < rows.length; i++) {
let rowItems = rows[i].childNodes;
for (let j = 0; j < rowItems.length; j++) {
if (currBtn === rowItems[j].textContent) {
curr = rowItems[j].parentElement;
}
}
}
let id;
for (let i = 0; i < rows.length; i++) {
if (curr.textContent === rows[i].textContent) {
id = i;
}
}
if (level === id) {
if (id % 2 === 0) {
setIsApp(true);
if (selected.length === 0) {
setSelected([...selected, data[currBtn].versions]);
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = selected[selected.length - 1];
setSelected([...selected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(level + 1);
} else {
let newSelected = selected.slice(0, id);
if (id % 2 === 0) {
setIsApp(true);
if (newSelected.length === 0) {
setSelected([...newSelected, data[currBtn].versions]);
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].versions]);
}
} else {
let lastSelected = newSelected[newSelected.length - 1];
setSelected([...newSelected, lastSelected[currBtn].child]);
setIsApp(false);
}
setPrevLevel(level);
setLevel(id + 1);
}
setFlag(true);
};
useEffect(() => {
let fields = [];
let lastSelected = selected[selected.length - 1];
if (level % 2 !== 0) {
fields = Object.keys(lastSelected).map((key) => {
let path = lastSelected[key].path;
let module = lastSelected[key].module_name;
let info = 'module: ' + module + ' path: ' + path;
return [key, info];
});
} else {
if (selected.length !== 0)
fields = Object.keys(lastSelected).map((key) => {
let info = lastSelected[key].info;
return [key, info];
});
}
if (fields.length > 0) {
if (level > prevLevel) {
setCards((prevState) => [...prevState, fields]);
} else {
setCards((prevState) => [
...prevState.slice(0, selected.length),
fields,
]);
}
}
}, [selected, level, prevLevel]);
useEffect(() => {
let fields = Object.keys(data).map((key) => {
let info = data[key].info;
return [key, info];
});
setCards([fields]);
setLevel(0);
}, [data]);
useEffect(() => {
setLevel(0);
setPrevLevel(-1);
setSelected([]);
}, [partition]);
if (cards) {
return (
<div ref={cardsRef}>
{cards.map((card, index) => (
<StackRow
data={card}
partition={partition}
level={index}
index={cards.indexOf(card)}
onClick={handleClick}
getInfo={getInfo}
isApp={isApp}
/>
))}
</div>
);
} else return null;
}
EDIT: Added data sample
{
cmake: {
info: "A cross-platform, open-source build system. CMake is a family of tools designed to build, test and package software.",
versions: {
"3.17.3": {
child: {},
module_name: "cmake/3.17.3",
path: "/opt/apps/nfs/spack/var/spack/environments/matador/modules/linux-centos8-x86_64/Core/cmake/3.17.3.lua",
version_no: "3.17.3"
}
}
},
gcc: {
info: "...",
versions: {
"8.4.0": {
child: {
cmake: {...},
cuda: {...},
cudnn: {...},
openmpi: {...},
.
.
.
},
module_name: "...",
path: "...",
version_no: "..."
}
"9.3.0": {...},
"10.1.0": {...}
}
}
}
When I call getCityName component will unmount and DidMount again and again, unless I remove async .All the code is running in nextjs.
this.state = {
bank_account: {
// bank_name: '',
// number: '',
// city: '',
// branch_name: ''
},
allCity: []
};
componentDidMount() {
const { owner_cellphone } = this.props;
this.getDraft(owner_cellphone);
this.fetchCity();
}
fetchCity = async () => {
const { data, error } = await getCity();
if (error) {
return;
}
console.log(data);
this.setState({ allCity: data });
};
getCityName = cityString => {
const { allCity } = this.state;
console.log(allCity);
if (!allCity || !cityString) {
return;
}
const cityArray = cityString.split(' ');
console.log(cityArray);
const targetProvince = allCity.find(item => item.code === cityArray[0]);
const targetCity = targetProvince.children.find(item => item.code === cityArray[0]);
return targetProvince.name + targetCity.name;
};
render() {
const { bank_account } = this.state;
const cityValue = this.getCityName(bank_account.city);
return (
<Item label="开户城市" icon={<Icon type="arrow-right" />} onClick={this.showCitySelect}>
<input
className="item-picker-input"
value={cityValue}
/>
</Item>
);
}
The reason it's not working because you are calling a async function from a sync function.
I am not sure it would work, but you can try..
getCityName = async (cityString) => {
const { allCity } = this.state;
console.log(allCity);
if (!allCity || !cityString) {
return;
}
const cityArray = cityString.split(' ');
console.log(cityArray);
const targetProvince = allCity.find(item => item.code === cityArray[0]);
const targetCity = targetProvince.children.find(item => item.code === cityArray[0]);
return targetProvince.name + targetCity.name;
};
render = async () => {
const { bank_account } = this.state;
const cityValue = await this.getCityName(bank_account.city);
return (
<Item label="开户城市" icon={<Icon type="arrow-right" />} onClick={this.showCitySelect}>
<input
className="item-picker-input"
value={cityValue}
/>
</Item>
);
}
getCityName = cityString => {
const { allCity } = this.state;
if (allCity === [] || !cityString) {
return;
}
const cityArray = cityString.split(' ');
let targetProvince = allCity.find(item => item.code === cityArray[0]);
if (targetProvince) {
let newProvince = JSON.parse(JSON.stringify(targetProvince));
const targetCity = newProvince.children.find(item => item.code === cityArray[1]);
return `${targetProvince.name} ${targetCity.name}`;
}
return '';
};
I think it might be a problem of deep copy.
I have array of data, which needed to render array of one and the same component which has window event listener. But if we have 20 event listeners on page then we have super freezing on page. How to have one event listener on array of one and the same component?
class BmResponsiveMenuButton extends PureComponent {
lastWidth = 0
resizeStep = 0
state = {
hidden: false
}
componentDidMount() {
//HERE ADDING EVENT LISTENERS (TOTALLY WE HAVE 15-20 LISTENERS ON PAGE)
window.addEventListener('resize', this.handleResize)
window.addEventListener('scroll', this.handleScroll, false)
this.anchorMenuButton = document.getElementById(this.props.moreMenuButtonId)
}
handleResize = () => {
this.handleHiddenOrNot()
}
componentDidUpdate() {
this.lastWidth = document.documentElement.clientWidth
}
handleHiddenOrNot = () => {
this.resizeStep = Math.abs(this.lastWidth - document.documentElement.clientWidth)
let boundingMenu = this.anchorMenuButton.getBoundingClientRect()
let boundingButton = this.anchorEl.getBoundingClientRect()
if (boundingButton.right > boundingMenu.left - 10) {
this.setState({
hidden: true
})
this.props.onHide(this.props.id)
} else {
this.setState({
hidden: false
})
this.props.onUnHide(this.props.id)
}
}
handleScroll = () => {
this.handleResize()
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize)
window.removeEventListener('scroll', this.handleScroll, false)
}
handleExited = () => {
let elem = this.anchorEl
elem.style.opacity = '0'
elem.style.visibility = 'hidden'
}
handleEnter = () => {
let elem = this.anchorEl
elem.style.opacity = '1'
elem.style.visibility = 'visible'
elem.style.position = 'sticky'
}
render() {
const {
title,
url,
classes,
target,
effects,
effectType,
index,
active,
currentModule,
module
} = this.props
const transitionProps = {
key: title,
in: !this.state.hidden,
enter: true,
exit: true,
timeout: !effects ?
0 : {
enter: this.resizeStep > 200 ? index * 100 : 300,
exit: 200
},
onEnter: () => this.handleEnter(),
onExited: () => this.handleExited()
}
let activeModule
if (module && currentModule === module) {
activeModule = true
} else if (active) {
activeModule = active
} else {
activeModule = url.includes(window.location.pathname + window.location.search + window.location.hash)
}
const ButtonComponent = ( <
Link to = {
url
}
target = {
target
}
innerRef = {
(node) => (this.anchorEl = node)
} >
<
Button className = {
classNames(classes.topNavigationButton, activeModule ? classes.selected : '')
} > {
title
} <
/Button> < /
Link >
)
switch (effectType) {
case 'slide':
return ( <
Slide direction = {
'left'
} { ...transitionProps
} > {
ButtonComponent
} <
/Slide>
)
case 'fade':
return <BmFade { ...transitionProps
} > {
ButtonComponent
} < /BmFade>
case 'grow':
return <BmGrow { ...transitionProps
} > {
ButtonComponent
} < /BmGrow>
default:
break
}
}
}
Edited with code example. I use this component for displaying or hiding button if it not fit in window. I comment place where i create eventListeners