-1

I have an nested object in React functional component state, but I only can access first level of object if I use any type.

export default function Detail() {
    const [user, setUser] = useState<any>({});
    const { id } = useParams();


    useEffect(() => {
        fetch('http://localhost:4000/users/' + id)
        .then(response => {
            return response.json();
        })
        .then(data => {
            setUser(data);
        })
    }, []);
    
    

    return (
        <div>
            <h1>Detail view</h1>
            <p>ID: { user.id }</p>
            <p>First name: { user.first_name }</p>
            <p>Last name: { user.last_name }</p>
            <p>Email: { user.email }</p>
            <p>Gender: { user.gender }</p>
        </div>
    );
}

When trying to access user.company.name it throws

Detail.tsx:40 Uncaught TypeError: Cannot read properties of undefined (reading 'name')

I made an interface for it, but it gives me an error if I'm trying to use it for state type.

interface UserInfo {
    id: number;
    first_name: string;
    last_name: string;
    email: string;
    gender: string;
    company: {
        name: string;
        department: string;
    }
}
Argument of type '{}' is not assignable to parameter of type 'UserInfo | (() => UserInfo)'.

How can I use defined interface as state type?

bordax
  • 77
  • 8
  • A quick fix is to use `{user.company &&

    {user.company.name}

    }` to prevent the error. Not a typescript expert, but you probably need to assign an intial object that already has data for the interface properties. See also here: https://stackoverflow.com/questions/36745013/interface-states-and-props-in-typescript-react
    –  Feb 13 '22 at 10:26
  • also there is optional chaining. not sure if possible in Ts – cmgchess Feb 13 '22 at 10:30

1 Answers1

3

Don't use the any type. TypeScript can't help you if you don't use actual types. Type the state narrowly.

Don't use an empty object as your default value. It isn't useful. Use a value that clearly indicates that the data isn't available yet.


type PossibleUserInfo = UserInfo | null;

const [user, setUser] = useState<PossibleUserInfo>(null);

Then, handle the case where you don't have the data yet explicitly.

It isn't useful to spit out a div with a bunch of paragraphs containing a label but not data.


if (user) {
    return (
        <div>
            <h1>Detail view</h1>
            <p>ID: { user.id }</p>
            <p>First name: { user.first_name }</p>
            <p>Last name: { user.last_name }</p>
            <p>Email: { user.email }</p>
            <p>Gender: { user.gender }</p>
        </div>
    );
}

return (
    <div>
        <h1>Default view</h1>
        <Loading />
    </div>
);

… where Loading is a component that shows (for example) a loading spinner.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • I tried ```UserInfo | null``` before but it didn't worked, because I didn't used conditional statement on return. Now it works. Thanks – bordax Feb 13 '22 at 10:36