Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input always losing focus when setNewState to data array #420

Closed
Taymindis opened this issue Feb 13, 2020 · 10 comments
Closed

Input always losing focus when setNewState to data array #420

Taymindis opened this issue Feb 13, 2020 · 10 comments

Comments

@Taymindis
Copy link

Taymindis commented Feb 13, 2020

please try this codesand box, when you key something on textbox, it loses focus

https://codesandbox.io/s/react-window-x9gwc

@Taymindis
Copy link
Author

react virtualized has no issue at all

https://codesandbox.io/s/react-virtualized-list-with-width-100-7btt7

@bvaughn
Copy link
Owner

bvaughn commented Feb 13, 2020

Don't define your item renderer inline. It will get recreated each time the parent component re-renders which will mess with text selection apparently.

https://codesandbox.io/s/react-window-5yzbr

@ZeeCoder
Copy link

ZeeCoder commented Sep 7, 2021

If this is the case the docs should probably reflect it:
https://react-window.vercel.app/#/api/FixedSizeList

This just tripped me and had no idea why my rows were unmounting.

@glitcher93
Copy link

glitcher93 commented Sep 7, 2022

Hi I seem to be having a similar issue where my inputs are losing focus after typing one character and the scrollbar jumps up immediately after losing focus

This is what my code looks like:
`
const FormAccordion = ({ title, section }: IProps) => {

const dispatch = useDispatch<AppDispatch>();

const location = useLocation();

const path = location.pathname === '/new-project'

const classes = useStyles();

let items: IProjectItems;
let quantities: IProjectQuantities;
let accordionExpand: IAccordionExpand;
let allItems: ILineItem[]
let projectDetails: IProjectDetails;

if (path) {
    allItems = useSelector(selectAllItems);
    items = useSelector(selectItems);
    quantities = useSelector(selectQuantities);
    accordionExpand = useSelector(selectAccordionExpand);
    projectDetails = useSelector(selectProjectDetails);
} else {
    allItems = useSelector(selectAllItemsEdit);
    items = useSelector(selectItemsEdit);
    quantities = useSelector(selectQuantitiesEdit);
    accordionExpand = useSelector(selectAccordionExpandEdit);
    projectDetails = useSelector(selectProjectDetailsEdit);
}

const { projectManager, projectManagerEmail, title: projectTitle, addressLineOne, city, province, postalCode, numberOfUnits, unitNumbersArr, unitSqftArr, budget, suiteType } = projectDetails;

const accordionDisable = !projectManager || !projectManagerEmail || !projectTitle || !addressLineOne || !city || !province || !postalCode || !numberOfUnits || !unitNumbersArr.length || !unitSqftArr.length || !budget || !suiteType;

const {
    entireUnitQuantities,
    preConQuantities,
    demoQuantities,
    livingDiningQuantities,
    kitchenQuantities,
    bedroomQuantities,
    bathroomQuantities,
    powderQuantities,
    miscQuantities
} = quantities;

const {
    entireUnit,
    preConstruction,
    demolition,
    livingDining,
    kitchen,
    bedroom,
    bathroom,
    powder,
    misc
} = items

const {
    entireUnitExpand,
    preConExpand,
    demoExpand,
    livingDiningExpand,
    kitchenExpand,
    bedroomExpand,
    bathroomExpand,
    powderExpand,
    miscExpand
} = accordionExpand;

const itemsArr: (category: string) => ILineItem[] = useCallback((category: string) => allItems.filter((item: ILineItem) => item.category === category), [allItems]);

let quantitiesArray: number[];
let sectionExpand: boolean;
let itemsArray: (string | null)[];
let itemMap: ILineItem[];

if (section === 'entireUnit') {
    quantitiesArray = entireUnitQuantities;
    sectionExpand = entireUnitExpand;
    itemsArray = entireUnit;
    itemMap = itemsArr('Entire Unit');
} else if (section === 'preCon') {
    quantitiesArray = preConQuantities;
    sectionExpand = preConExpand;
    itemsArray = preConstruction;
    itemMap = itemsArr('Pre-Construction Prep');
} else if (section === 'demo') {
    quantitiesArray = demoQuantities;
    sectionExpand = demoExpand;
    itemsArray = demolition;
    itemMap = itemsArr('Demolition');
} else if (section === 'livingDining') {
    quantitiesArray = livingDiningQuantities;
    sectionExpand = livingDiningExpand;
    itemsArray = livingDining;
    itemMap = itemsArr('Living Room/Dining Room');
} else if (section === 'kitchen') {
    quantitiesArray = kitchenQuantities;
    sectionExpand = kitchenExpand;
    itemsArray = kitchen;
    itemMap = itemsArr('Kitchen');
} else if (section === 'bedroom') {
    quantitiesArray = bedroomQuantities;
    sectionExpand = bedroomExpand;
    itemsArray = bedroom;
    itemMap = itemsArr('Bedrooms');
} else if (section === 'bathroom') {
    quantitiesArray = bathroomQuantities;
    sectionExpand = bathroomExpand;
    itemsArray = bathroom;
    itemMap = itemsArr('Bathrooms');
} else if (section === 'powder') {
    quantitiesArray = powderQuantities;
    sectionExpand = powderExpand;
    itemsArray = powder;
    itemMap = itemsArr('Powder Room');
} else {
    quantitiesArray = miscQuantities;
    sectionExpand = miscExpand;
    itemsArray = misc;
    itemMap = itemsArr('Misc');
}

const handleQuantityChange = useCallback((e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, position: number, id: string) => {
    const { name, value } = e.target;

    if (path) {
        dispatch(changeQuantity({
            name,
            value,
            position,
            id
        }));
    }
    if (!path) {
        dispatch(changeQuantityEdit({
            name,
            value,
            position,
            id
        }));
    }
}, [entireUnitQuantities, preConQuantities, demoQuantities, livingDiningQuantities, kitchenQuantities, bedroomQuantities, bathroomQuantities, powderQuantities, miscQuantities])

const handleOnAdd = useCallback((id: string, position: number, category: string, price: number, name?: string) => {
    if (path) {
        dispatch(addItem({id, position, category}));
        dispatch(addPrice({position, category, price, name}));
    }
    if (!path) {
        dispatch(addItemEdit({id, position, category}));
        dispatch(addPriceEdit({position, category, price, name}));
    }
}, [entireUnit, preConstruction, demolition, livingDining, kitchen, bedroom, bathroom, powder, misc])

const handleOnRemove = useCallback((position: number, category: string, price: number, name?: string) => {
    if (path) {
        dispatch(subtractPrice({position, category, price, name}));
        dispatch(removeItem({position, category}));
    }
    if (!path) {
        dispatch(subtractPriceEdit({position, category, price}));
        dispatch(removeItemEdit({position, category, name}));
    }
}, [entireUnit, preConstruction, demolition, livingDining, kitchen, bedroom, bathroom, powder, misc, entireUnitQuantities, preConQuantities, demoQuantities, livingDiningQuantities, kitchenQuantities, bedroomQuantities, bathroomQuantities, powderQuantities, miscQuantities])

const handleIncrement = useCallback((position: number, category: string) => {
    if (path) {
        dispatch(increment({position, category}));
    }
    if (!path) {
        dispatch(incrementEdit({position, category}));
    }
    
}, [entireUnitQuantities, preConQuantities, demoQuantities, livingDiningQuantities, kitchenQuantities, bedroomQuantities, bathroomQuantities, powderQuantities, miscQuantities])

const handleDecrement = useCallback((position: number, category: string) => {
    if (path) {
        dispatch(decrement({position, category}));
    }
    if (!path) {
        dispatch(decrementEdit({position, category}));           
    }
}, [entireUnitQuantities, preConQuantities, demoQuantities, livingDiningQuantities, kitchenQuantities, bedroomQuantities, bathroomQuantities, powderQuantities, miscQuantities])

const handleOpen = useCallback(() => {
    if (path) {
        dispatch(open({sectionName: section}));
    } else {
        dispatch(openEdit({sectionName: section}));           
    }
}, [entireUnitExpand, preConExpand, demoExpand, livingDiningExpand, kitchenExpand, bedroomExpand, bathroomExpand, powderExpand, miscExpand])

const handleClose = useCallback(() => {
    if (path) {
        dispatch(close({sectionName: section}));
    } else {
        dispatch(closeEdit({sectionName: section}));           
    }
}, [entireUnitExpand, preConExpand, demoExpand, livingDiningExpand, kitchenExpand, bedroomExpand, bathroomExpand, powderExpand, miscExpand])

const determineLabelName = (name: string) => {
    if (name?.includes('SQFT')) {
        return 'SQFT';
    } else if (name?.includes('LNFT')) {
        return 'LNFT';
    } else {
        return 'Quantity';
    }
}

const Row = useCallback(({index, style}: {index: number, style: CSSProperties | undefined}) =>  {
    const { id, name, price } = itemMap![index] || {};

    return (
        <div
        style={style}
        >
            <div
            className={classes.lineItemContainer}
            key={id}
            >
                <FormLabel 
                sx={theme => ({
                    width: '60%',
                    fontSize: `${theme.typography.pxToRem(16)}`,
                    fontWeight: 600,
                    [theme.breakpoints.up('md')]: {
                        fontSize: `${theme.typography.pxToRem(18)}`,
                        width: '50%'
                    }
                })}
                >
                    {name}
                </FormLabel>
                <div
                className={classes.textfieldContainer}
                >
                    <IconButton
                    onClick={() => handleIncrement(index, section!)}
                    disabled={itemsArray!.includes(id)}
                    >
                        <ArrowDropUp 
                        sx={theme => ({
                            fontSize: `${theme.typography.pxToRem(20)}`,
                            [theme.breakpoints.up('md')]: {
                                fontSize: `${theme.typography.pxToRem(28)}`
                            }
                        })}
                        />
                    </IconButton>
                    <TextField
                    variant='standard' 
                    label={determineLabelName(name)}
                    type='number'
                    inputProps={{
                        inputMode: 'numeric',
                        pattern: "[0-9]*",
                        min: 1,
                        sx: (theme: Theme) => ({
                            fontSize: `${theme.typography.pxToRem(16)}`,
                            [theme.breakpoints.up('md')]: {
                                fontSize: `${theme.typography.pxToRem(18)}`
                            }
                        })
                    }}
                    InputLabelProps={{
                        sx: (theme) => ({
                            fontSize: `${theme.typography.pxToRem(16)}`,
                            fontWeight: 600,
                            [theme.breakpoints.up('md')]: {
                                fontSize: `${theme.typography.pxToRem(18)}`
                            }
                        })
                    }}
                    InputProps={{
                        sx: (theme) => ({
                            fontSize: `${theme.typography.pxToRem(16)}`,
                            [theme.breakpoints.up('md')]: {
                                fontSize: `${theme.typography.pxToRem(18)}`
                            }
                        })
                    }}
                    name={`${section}-${index}`}
                    sx={theme => ({
                        width: `${theme.typography.pxToRem(80)}`,
                        height:`${theme.typography.pxToRem(60)}`,
                        [theme.breakpoints.up('md')]: {
                            width: `${theme.typography.pxToRem(100)}`
                        }
                    })}
                    disabled={itemsArray!.includes(id)}
                    value={quantitiesArray![index] === 0 ? "" : quantitiesArray![index]}
                    onChange={(e) => {
                        handleQuantityChange(e, index, id)
                    }
                    }
                    />
                    <IconButton
                    onClick={() => handleDecrement(index, section!)}
                    disabled={itemsArray!.includes(id)}
                    >
                        <ArrowDropDown 
                        sx={theme => ({
                            fontSize: `${theme.typography.pxToRem(20)}`,
                            [theme.breakpoints.up('md')]: {
                                fontSize: `${theme.typography.pxToRem(28)}`
                            }
                        })}
                        />
                    </IconButton>
                </div>
                <div
                className={classes.buttonContainer}
                >
                    <IconButton
                    disabled={quantitiesArray![index] !== 0 && !itemsArray!.includes(id) ? false : true}
                    onClick={() => {
                        handleOnAdd(id, index, section!, price, name)
                    }}
                    >
                        <AddCircleOutline 
                        sx={theme => ({
                            fontSize: `${theme.typography.pxToRem(18)}`,
                            [theme.breakpoints.up('md')]: {
                                fontSize: `${theme.typography.pxToRem(24)}`
                            }
                        })}
                        />
                    </IconButton>
                    <IconButton
                    disabled={itemsArray!.includes(id) ? false : true}
                    onClick={() => {
                        handleOnRemove(index, section!, price, name);
                    }}
                    >
                        <CancelOutlined 
                        sx={theme => ({
                            fontSize: `${theme.typography.pxToRem(18)}`,
                            [theme.breakpoints.up('md')]: {
                                fontSize: `${theme.typography.pxToRem(24)}`
                            }
                        })}
                        />
                    </IconButton>
                </div>
            </div>
        </div>
    )
}, [entireUnit, preConstruction, demolition, livingDining, kitchen, bedroom, bathroom, powder, misc, entireUnitQuantities, preConQuantities, demoQuantities, livingDiningQuantities, kitchenQuantities, bedroomQuantities, bathroomQuantities, powderQuantities, miscQuantities])

return (
    <Accordion
        sx={{
            backgroundColor: "transparent",
            boxShadow: 'none',
            borderTop: '1px solid #000',
        }}
        square={true}
        disableGutters
        expanded={sectionExpand}
        // disabled={accordionDisable}
        >
            <AccordionSummary
            expandIcon={
                <ArrowForwardIosSharp 
                sx={theme => ({
                    fontSize: `${theme.typography.pxToRem(20)}`,
                })}
                onClick={sectionExpand === false ? handleOpen : handleClose}
                />
            }
            sx={{
                flexDirection: 'row-reverse',
                transition: 'color 0.3s',
                width: '100%',
                '&:hover': {
                    color: '#E34234'
                },
                '& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
                    transform: 'rotate(90deg)',
                }
            }}
            classes={{
                content: classes.accordionSummaryContent
            }}
            >
                <h2
                className={classes.accordionHeader}
                >
                    {title}
                </h2>
            </AccordionSummary>
            <AccordionDetails
            sx={{
                display: 'flex',
                flexDirection: 'column'
            }}
            >
                <FixedSizeList
                height={400}
                width={"100%"}
                itemSize={75}
                itemCount={itemMap!.length}
                itemKey={index => itemMap[index].id}
                >
                    {Row}
                </FixedSizeList>
                <Button
                variant='text'
                disableRipple
                sx={theme => ({
                    display: "flex",
                    alignItems: "center",
                    alignSelf: 'flex-end',
                    fontSize: `${theme.typography.pxToRem(14)}`,
                    [theme.breakpoints.up('md')]: {
                        fontSize: `${theme.typography.pxToRem(16)}`
                    }
                })}
                onClick={handleClose}
                >
                    Close
                </Button>
            </AccordionDetails>
        </Accordion>
)

}`

@bvaughn
Copy link
Owner

bvaughn commented Sep 7, 2022

The same answer applies as above: Don't define your renderer inline.

Any time any of the things in your dependencies array change (entireUnit, preConstruction, demolition, livingDining, kitchen, bedroom, bathroom, powder, misc, entireUnitQuantities, preConQuantities, demoQuantities, livingDiningQuantities, kitchenQuantities, bedroomQuantities, bathroomQuantities, powderQuantities, miscQuantities) your renderer will be recreated, and state for things like text inputs will be lost.

@glitcher93
Copy link

The same answer applies as above: Don't define your renderer inline.

Any time any of the things in your dependencies array change (entireUnit, preConstruction, demolition, livingDining, kitchen, bedroom, bathroom, powder, misc, entireUnitQuantities, preConQuantities, demoQuantities, livingDiningQuantities, kitchenQuantities, bedroomQuantities, bathroomQuantities, powderQuantities, miscQuantities) your renderer will be recreated, and state for things like text inputs will be lost.

can you maybe elaborate more on this? im not exactly sure how to tackle this.

@bvaughn
Copy link
Owner

bvaughn commented Sep 7, 2022

Did you check out the comment above before the issue was closed?

#420 (comment)

They link to a suggested solution.

@glitcher93
Copy link

Did you check out the comment above before the issue was closed?

#420 (comment)

They link to a suggested solution.

Yes, but the codesandbox isn't working in it, so its not really providing any clues

@glitcher93
Copy link

Did you check out the comment above before the issue was closed?

#420 (comment)

They link to a suggested solution.

I think I see it now, the item renderer shouldn't be inside the component but outside the component, am I correct in that assumption?

@mayanksolan
Copy link

Did you check out the comment above before the issue was closed?
#420 (comment)
They link to a suggested solution.

I think I see it now, the item renderer shouldn't be inside the component but outside the component, am I correct in that assumption?

Yes, that is how the issue is resolved, the item renderer shouldn't be inside the component but outside the component

WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the rendered function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the rendered function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit ensures that the renderer function is defined
at the top-level, and not inline.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.

This does not appear to have functional impact as of now,
but will come in handy after #4376.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Dec 24, 2024
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.

This does not appear to have functional impact as of now,
but will come in handy after #4376.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Jan 12, 2025
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Jan 12, 2025
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Jan 12, 2025
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Jan 12, 2025
...upon re-render.
See bvaughn/react-window#420 (comment).

This commit ensures that the renderer function is defined
at the top-level, and not inline.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Jan 12, 2025
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.

This does not appear to have functional impact as of now,
but will come in handy after #4376.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Jan 12, 2025
See bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definition
from inline to top-level.

This does not appear to have functional impact as of now,
but will come in handy after #4376.
WofWca added a commit to deltachat/deltachat-desktop that referenced this issue Jan 12, 2025
...upon re-render.
And a re-render can happen when new items get loaded
in `InfiniteLoader`.
Addresses this issue: bvaughn/react-window#420 (comment).

This commit simply moves the renderer function definitions
from inline to top-level.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants