stream-chat-react-native-core
Version:
The official React Native and Expo components for Stream Chat, a service for building chat applications
112 lines (94 loc) • 3.93 kB
text/typescript
import { Channel, StateStore } from 'stream-chat';
import { useStateStore } from '../hooks/useStateStore';
import type { File } from '../types/types';
export type EditChannelImagePendingAction = 'camera' | 'library' | 'reset';
export type EditChannelDetailsState = {
/** Current value of the name input. */
currentName: string;
/**
* Channel image URL snapshotted at construction; not updated by WS events.
*/
initialImage: string | undefined;
/**
* Channel name snapshotted at construction; not updated by WS events.
*/
initialName: string;
/** Pending action from the {@link ChannelEditImageSheet}, or `null` when idle. */
pendingAction: EditChannelImagePendingAction | null;
/** `undefined` = untouched, `File` = newly picked, `null` = reset. */
updatedImage: File | null | undefined;
};
/**
* Holds the editable state for the channel details form (name + image) plus the
* pending image-picker action. The channel's name and image are snapshotted
* once at construction and are intentionally **not** updated by WebSocket
* events, so an inbound `channel.updated` does not clobber the user's
* in-progress edits. Leaf components can subscribe to narrow slices via
* {@link useStateStore}.
*/
export class EditChannelDetailsStore {
public state: StateStore<EditChannelDetailsState>;
constructor(channel: Channel) {
const initialName = channel.data?.name ?? '';
const initialImage = channel.data?.image;
this.state = new StateStore<EditChannelDetailsState>({
currentName: initialName,
initialImage,
initialName,
pendingAction: null,
updatedImage: undefined,
});
}
/** Updates the current value of the name input. */
setCurrentName(currentName: string) {
this.state.partialNext({ currentName });
}
/**
* Updates the picked image. `undefined` = untouched, `File` = newly picked,
* `null` = reset.
*/
setUpdatedImage(updatedImage: File | null | undefined) {
this.state.partialNext({ updatedImage });
}
/** Sets the pending image-picker action, or `null` to clear it. */
setPendingAction(pendingAction: EditChannelImagePendingAction | null) {
this.state.partialNext({ pendingAction });
}
}
/** Whether the name input differs from the channel's initial name. */
export const isNameEdited = (state: EditChannelDetailsState) =>
state.currentName !== state.initialName;
/**
* Whether the image has unsaved changes. The image is edited once touched
* (`updatedImage !== undefined`), except when both the initial and updated
* image are falsy (no image before, none now).
*/
export const isImageEdited = (state: EditChannelDetailsState) =>
!(state.updatedImage === undefined || (!state.updatedImage && !state.initialImage));
const selectIsNameEdited = (state: EditChannelDetailsState) => ({
isNameEdited: isNameEdited(state),
});
const selectIsImageEdited = (state: EditChannelDetailsState) => ({
isImageEdited: isImageEdited(state),
});
const selectAreChannelDetailsEdited = (state: EditChannelDetailsState) => ({
areChannelDetailsEdited: isNameEdited(state) || isImageEdited(state),
});
/**
* Subscribes to an {@link EditChannelDetailsStore} and returns whether the name
* input has unsaved changes.
*/
export const useIsNameEdited = (store: EditChannelDetailsStore) =>
useStateStore(store.state, selectIsNameEdited).isNameEdited;
/**
* Subscribes to an {@link EditChannelDetailsStore} and returns whether the image
* has unsaved changes.
*/
export const useIsImageEdited = (store: EditChannelDetailsStore) =>
useStateStore(store.state, selectIsImageEdited).isImageEdited;
/**
* Subscribes to an {@link EditChannelDetailsStore} and returns whether the form
* has any unsaved changes (name or image).
*/
export const useAreChannelDetailsEdited = (store: EditChannelDetailsStore) =>
useStateStore(store.state, selectAreChannelDetailsEdited).areChannelDetailsEdited;