By using useState
in your viewmodel, you're really making it a sort of custom hook. I would argue that storing state isn't really a viewmodel concern. You also have a hidden dependency between the getUsersList
and usersList
where usersList
starts out empty and will remain so until the consumer calls getUsersList
. And by having a useState
in your viewmodel, you start having restrictions on when and where you can call it: it can now only be created at the top level of your component.
Option 1
Commit to making this a custom hook and move the useEffect
inside of it. You no longer really need a separate getUsersList
function, so just make that your effect.
Either instantiate your GetUserListUseCase
inside the effect, outside the hook, or use a useMemo
so that it remains stable.
function useUsersList() {
const [usersList, setUsersList] = useState<User[]>([]);
useEffect(() => {
const getListUseCase = new GetUserListUseCase(new UserRepositoryImpl());
getListUseCase.invoke().then(setUsersList);
}, []);
return usersList;
}
function UserListView() {
const usersList = useUsersList();
return (
<>
usersList...
</>
);
}
Option 2
If you still want a separate viewmodel, you can do it but make it only responsible for constructing the UseCase and Repository and then fetching the data, and just return the results rather than setting state.
Like option 1, either instantiate your UserListViewModel
inside the effect, outside the component, or use a useMemo
so that it remains stable.
You could even put the useState
/useEffect
in a custom hook that only returns the usersList
if you want to combine the two and keep the view clean.
function UserListViewModel() {
const getListUseCase = new GetUserListUseCase(new UserRepositoryImpl());
return {
async getUsersList() {
try {
const result = await getListUseCase.invoke();
return result;
} catch (err) {
throw err;
}
}
};
}
function useUsersList() {
const [usersList, setUsersList] = useState<User[]>([]);
useEffect(() => {
const { getUsersList } = UserListViewModel();
getUsersList().then(setUsersList);
}, []);
return usersList;
}
function UserListView() {
const usersList = useUsersList();
return (
<>
usersList...
</>
);
}
Option 3
If you do want to keep your viewmodel design the same, you could still just use a useCallback
for the getUsersList
in your viewmodel and specify it as a dependency in your effect. However, then you would get another warning about a missing dependency inside that useCallback
(this time for getListUseCase
). You could resolve that like before, either instantiating your GetUserListUseCase
inside the getUsersList
, outside the viewmodel entirely, or with a useMemo
:
const getUsersList = useCallback(async function getUsersList() {
try {
const getListUseCase = new GetUserListUseCase(new UserRepositoryImpl());
const result = await getListUseCase.invoke();
setUsersList(result);
} catch (err) {
throw err;
}
}, []);