React useState – getting stuck in generated data input form

I am developing a web application using React (React Bootstrap, Typescript, SCSS). I have made progress; I fetch and display the items from my application server, I can search and I can open the item screen with all the details.

But now for the part where I am definitely getting confused, even after searching the internet. I am creating a page for editing an item. My application consists of modules (the application is Data Crow by the way). Each module has fields. Each field has a type.

So when I build up my page, I need to loop through the fields of the selected module.
I have made a context for the module (useModule). The page itself is sitting in an authorization context (RequireAuth).

Now, as you can see below I then loop though the fields and for each field I call a function (sitting in a separate .tsx file) to render the component. I kind of have to it this way as I have many module and user can define their own modules and fields.

So this is the item page:

import { useLocation, useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import { fetchItem, fetchReferences, type Field, type Item, type References } from "../../services/datacrow_api";
import { RequireAuth } from "../../context/authentication_context";
import { useModule } from "../../context/module_context";
import { InputField } from "../../components/input/component_factory";
import { Button } from "react-bootstrap";
import Form from 'react-bootstrap/Form';

export function ItemPage() {
    const currentModule = useModule();

    useEffect(() => {
        currentModule.selectedModule && fetchItem(currentModule.selectedModule.index, state.itemID).then((data) => setItem(data));
    }, [currentModule.selectedModule]);

    useEffect(() => {
        currentModule.selectedModule && fetchReferences(currentModule.selectedModule.index).then((data) => setReferences(data));
    }, [currentModule.selectedModule]);
    
    const navigate = useNavigate();
    const { state } = useLocation();

    const [item, setItem] = useState<Item>();
    const [references, setReferences] = useState<References[]>();

    useEffect(() => {
        if (!state) {
            navigate('/');
        }
    }, []);

    const [validated, setValidated] = useState(false);
    
    function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
        event.preventDefault();
        event.stopPropagation();

        setValidated(true);
    };
    
    function ReferencesForField(field: Field) {
        var i = 0;
        while (i < references!.length) {
            if (references![i].moduleIdx === field.referencedModuleIdx)
                return references![i];

            i++;
        }

        return undefined;
    }

    return (
        <RequireAuth>
            <div style={{ display: "inline-block", width: "100%", textAlign: "left" }} key="item-details">
                <Form key="form-item-detail" noValidate validated={validated} onSubmit={handleSubmit} >
                    {references && item?.fields.map((fieldValue) => (
                        InputField(fieldValue.field, fieldValue.value, ReferencesForField(fieldValue.field))
                    ))}
                    
                    <Button type="submit">Save</Button>
                </Form>
            </div>
        </RequireAuth>
    );
}

So for each field the function InputField is called. This one looks as follows (work in progress hence the empty else if blocks):

import type { Field, References } from "../,,/../../services/datacrow_api";
import { Col, Form, Row } from "react-bootstrap";
import { DcTextField } from "./dc_textfield";
import { DcLongTextField } from "./dc_long_textfield";
import { DcCheckBox } from "./dc_checkbox";
import { DcUrlField } from "./dc_url_field";
import { DcDateField } from "./dc_datefield";
import { DcNumberField } from "./dc_numberfield";
import { DcDecimalField } from "./dc_decimalfield";
import { DcReferenceField } from "./dc_reference_field";

enum FieldType {
    CheckBox = 0,
    TextField = 1,
    LongTextField = 2,
    DropDown = 3,
    UrlField = 4,
    ReferencesField = 5,
    DateField = 6,
    FileField = 7,
    TagField = 8,
    RatingField = 9,
    IconField = 10,
    NumberField = 11,
    DecimalField = 12,
    DurationField = 13
}

function Field(field : Field, value : Object, references?: References) {
    if (field.type === FieldType.CheckBox) {
        return DcCheckBox(field, value);
    } else if (field.type === FieldType.TextField) {
        return DcTextField(field, value);
    } else if (field.type === FieldType.LongTextField) {
        return DcLongTextField(field, value);
    } else if (field.type === FieldType.DropDown) {
        return DcReferenceField(field, value, references);
    } else if (field.type === FieldType.UrlField) {
        return DcUrlField(field, value);
    } else if (field.type === FieldType.ReferencesField) {
    } else if (field.type === FieldType.DateField) {
        return DcDateField(field, value);
    } else if (field.type === FieldType.FileField) {
    } else if (field.type === FieldType.TagField) {
    } else if (field.type === FieldType.RatingField) {
    } else if (field.type === FieldType.IconField) {
    } else if (field.type === FieldType.NumberField) {
        return DcNumberField(field, value);
    } else if (field.type === FieldType.DecimalField) {
        return DcDecimalField(field, value);
    } else if (field.type === FieldType.DurationField) {
    }
}

export function InputField(field: Field, value: Object, references?: References) {
    return (
        <Col key={"detailsColField" + field.index}>
            {field.type != FieldType.CheckBox ?
                <Row key={"detailsRowLabelField" + field.index}>
                    <Form.Label
                        style={{ textAlign: "left" }}
                        className="text-secondary"
                        key={"label-" + field.index}
                        htmlFor={"field-" + field.index}>
                        {field.label}
                    </Form.Label>
                </Row>
                :
                ""}

            <Row key={"detailsRowInputField" + field.index} className="mb-3">
                <Form.Group>
                    {Field(field, value, references)}
                    <Form.Control.Feedback>ok</Form.Control.Feedback>
                </Form.Group>
            </Row>
        </Col>
    )
}

And now onto the problem at hand; I have a reference field (which is a React-Select component). I have also added this below. The only thing here is that I want to make a useState call to trigger an icon update for when the dropdown is collapsed. But this triggers the probably very well know error: React has detected a change in the order of Hooks called by ItemPage. This will lead to bugs and errors if not fixed. For more information (and a RTFM message, which I did and after which I did I get the feeling I am doomed).

This is what I am trying to add: const [selectedValue, setSelectedValue] = useState<null>();

I am I just completely doing the wrong thing? Am in a to deep a layer to make use of hooks here? That’s basically all I am looking for in an answer, more is appreciated but that is the essence of this post. Help…. 🙂

import Select, { components, type ActionMeta, type ControlProps, type GroupBase, type MultiValue, type OptionProps, type SingleValue } from 'react-select'
import { type Field, type References } from "../.././services/datacrow_api";
import type { JSX } from 'react/jsx-runtime';

export interface IconSelectOption {
    value: string;
    label: string;
    iconUrl: string;
}

export function DcReferenceField(field: Field, value: Object, references?: References) {
    const { Option } = components;

    const IconOption = (props: JSX.IntrinsicAttributes & OptionProps<IconSelectOption, boolean, GroupBase<IconSelectOption>>) => (
        <Option {...props}>
            <img
                src={props.data.iconUrl}
                style={{ width: 24 }}
            />
            {props.data.label}
        </Option>
    );

    function CurrentValue() {
        let idx = 0;
        let selectedIdx = -1;

        options.forEach((option) => {
            if (option.value === value)
                selectedIdx = idx;
            idx++;
        });

        return options[selectedIdx];
    }

    function Options() {
        let options: IconSelectOption[] = [];

        if (references && references.items) {
            references.items.map(reference =>
                options.push({ value: reference.id, label: reference.name, iconUrl: reference.iconUrl }),
            );
        }

        return options;
    }

    const options = Options();
    const currentValue = CurrentValue();

    const Control = ({ children, ...props }: ControlProps<IconSelectOption, boolean, GroupBase<IconSelectOption>>) => (
        <components.Control {...props}>
            <img src={currentValue.iconUrl} />
            {children}
        </components.Control>
    );

    function selectionChanged(newValue: SingleValue<IconSelectOption> | MultiValue<IconSelectOption>, actionMeta: ActionMeta<IconSelectOption>): void {

        if (!options || !newValue || !currentValue) return;

        if (Array.isArray(newValue)) {

        } else {
            //setSelectedValue(newValue as IconSelectOption);
        }
    }

    return (
        <Select
            className="basic-single"
            classNamePrefix="select"
            options={options}
            defaultValue={currentValue}
            onChange={selectionChanged}
            isClearable
            isSearchable
            placeholder="..."
            components={{ Option: IconOption, Control }} />
    );
}

Full error details:

React has detected a change in the order of Hooks called by ItemPage. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks

   Previous render            Next render
   ------------------------------------------------------
1. useContext                 useContext
2. useEffect                  useEffect
3. useEffect                  useEffect
4. useContext                 useContext
5. useContext                 useContext
6. useContext                 useContext
7. useContext                 useContext
8. useContext                 useContext
9. useContext                 useContext
10. useContext                useContext
11. useRef                    useRef
12. useContext                useContext
13. useLayoutEffect           useLayoutEffect
14. useCallback               useCallback
15. useContext                useContext
16. useContext                useContext
17. useState                  useState
18. useState                  useState
19. useEffect                 useEffect
20. useState                  useState
21. undefined                 useReducer
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 Component Stack: 
    ItemPage item_details.tsx:12
    RenderedRoute chunk-D52XG6IA.mjs:4355
    Outlet chunk-D52XG6IA.mjs:4976
    div unknown:0
    Layout unknown:0
    RenderedRoute chunk-D52XG6IA.mjs:4355
    Routes chunk-D52XG6IA.mjs:5041
    ModuleProvider module_context.tsx:16
    AuthProvider authentication_context.tsx:17
    App unknown:0
    Router chunk-D52XG6IA.mjs:4984
    BrowserRouter chunk-D52XG6IA.mjs:7059

New contributor

Robert W. is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

The starting point of the problem is this line:

InputField(fieldValue.field, fieldValue.value, ReferencesForField(fieldValue.field))

You are calling InputField as a function, which means it executes immediately, during the rendering of ItemPage. InputField then calls Field, which calls various other functions, and somewhere in one of those functions you must be calling react hooks. Since all this is taking place during the rendering of ItemPage, those are hooks of ItemPage.

The way that hooks are designed, you must always call the same number of hooks in the same order each time your component renders. This is called the Rules of Hooks. Your call to InputField is in a loop, so if the length of item.fields change, you will call InputField more times, which will in turn result in more hooks being called.

The fix for this is to create components and render them as elements. For example, we could do something like this with your InputField, making it take only one parameter, which is the props object

export function InputField({ 
  field, 
  value, 
  references
}: {
  field: Field, 
  value: Object, 
  references?: References
}) {
    return (
        <Col key={"detailsColField" + field.index}>
            {/* ... identical code to what you have ... */}
        </Col>
    )
}

And you’ll use it like this:

{references && item?.fields.map((fieldValue) => (
  <InputField 
    field={fieldValue.field} 
    value={fieldValue.value}
    references={ReferencesForField(fieldValue.field)}
  />
)}

This way, you are not calling InputField immediately, during the rendering of ItemPage. You’re just telling react “I would like to render these 6 (or however many) InputFields”. React will then set up its internal state and do separate renders for each of those input fields, and they can each call their own hooks.


Two additional comments:

  1. I just showed this example for InputField, but you will need to turn most of the other parts of your code into components as well. Components must take a single parameter, which is the props object, and you must render them as an element (ie, with the angle brackets < and >), not call them yourself.
  2. When you define components, they must not be inside other components. For example, your IconOption is defined inside DcReferenceField. As a result, every time DcReferenceField renders you create a brand new function named IconOption. It may have the same text as the previous one, but it’s a new function, so react will think it’s a different component type. This forces react to unmount the old instance and mount a new one, which wipes out any internal state. Instead, define all your components just once at the top level of the file.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật