Skip to content

Commit 14bb358

Browse files
authored
fix(kno-7523): fixes separation of zustand vanilla and react (#429)
* fixes type handling * testing new behavior * lint and format * Removes upgrade guide and validated store * remove console logs * removes test data
1 parent 0fc5f2d commit 14bb358

File tree

9 files changed

+55
-622
lines changed

9 files changed

+55
-622
lines changed

examples/client-example/src/App.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Knock from "@knocklabs/client";
2-
import { useEffect, useMemo } from "react";
2+
import { useEffect, useMemo, useState } from "react";
33

44
import "./App.css";
55

@@ -16,10 +16,10 @@ const useNotificationFeed = (knockClient, feedId) => {
1616
auto_manage_socket_connection: true,
1717
auto_manage_socket_connection_delay: 500,
1818
});
19-
const notificationStore = notificationFeed.store;
19+
2020
notificationFeed.fetch();
2121

22-
return [notificationFeed, notificationStore];
22+
return [notificationFeed, notificationFeed.store];
2323
}, [knockClient, feedId]);
2424
};
2525

@@ -28,6 +28,7 @@ function App() {
2828
knockClient,
2929
process.env.REACT_APP_KNOCK_CHANNEL_ID,
3030
);
31+
const [feedState, setFeedState] = useState(feedStore.getState());
3132

3233
useEffect(() => {
3334
knockClient.preferences
@@ -40,6 +41,20 @@ function App() {
4041
});
4142
}, []);
4243

44+
// Consume the store
45+
useEffect(() => {
46+
// What to do on updates
47+
const render = (state) => {
48+
setFeedState(state);
49+
};
50+
51+
// What to do on initial load
52+
render(feedStore.getInitialState());
53+
54+
// Subscribe to updates
55+
feedStore.subscribe(render);
56+
}, [feedStore]);
57+
4358
useEffect(() => {
4459
const teardown = feedClient.listenForUpdates();
4560

@@ -58,7 +73,7 @@ function App() {
5873
return () => teardown?.();
5974
}, [feedClient]);
6075

61-
const { loading, items, pageInfo } = feedStore((state) => state);
76+
const { loading, items, pageInfo } = feedState;
6277

6378
return (
6479
<div className="App">

packages/client/src/clients/feed/feed.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GenericData } from "@knocklabs/types";
22
import EventEmitter from "eventemitter2";
33
import { Channel } from "phoenix";
4-
import type { StoreApi, UseBoundStore } from "zustand";
4+
import type { StoreApi } from "zustand";
55

66
import Knock from "../../knock";
77
import { NetworkStatus, isRequestInFlight } from "../../networkStatus";
@@ -48,7 +48,7 @@ class Feed {
4848
private visibilityChangeListenerConnected: boolean = false;
4949

5050
// The raw store instance, used for binding in React and other environments
51-
public store: UseBoundStore<StoreApi<FeedStoreState>>;
51+
public store: StoreApi<FeedStoreState>;
5252

5353
constructor(
5454
readonly knock: Knock,
@@ -577,7 +577,6 @@ class Feed {
577577
metadata,
578578
}: FeedMessagesReceivedPayload) {
579579
this.knock.log("[Feed] Received new real-time message");
580-
581580
// Handle the new message coming in
582581
const { items, ...state } = this.store.getState();
583582
const currentHead: FeedItem | undefined = items[0];

packages/client/src/clients/feed/store.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { GenericData } from "@knocklabs/types";
2-
import { create } from "zustand";
2+
import { createStore as createVanillaZustandStore } from "zustand";
33

44
import { NetworkStatus } from "../../networkStatus";
55

@@ -34,7 +34,7 @@ const initialStoreState = {
3434
};
3535

3636
export default function createStore() {
37-
return create<FeedStoreState>((set) => ({
37+
return createVanillaZustandStore<FeedStoreState>()((set) => ({
3838
// Keeps track of all of the items loaded
3939
...initialStoreState,
4040
// The network status indicates what's happening with the request

packages/client/upgrade-guides/to-v0.12.0.md

-80
This file was deleted.

packages/expo/upgrade-guides/to-v0.3.0.md

-126
This file was deleted.

packages/react-core/src/modules/feed/hooks/useNotificationStore.ts

+32-29
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
11
import { Feed, type FeedStoreState } from "@knocklabs/client";
2+
import { type StoreApi, type UseBoundStore, useStore } from "zustand";
23

34
// A hook designed to create a `UseBoundStore` instance.
4-
// We used to have to do some extra work to do this, but
5-
// with zustand updated we can just use the feedClient.store
6-
// directly.
7-
function useCreateNotificationStore(feedClient: Feed) {
8-
return feedClient.store;
9-
}
10-
5+
// https://zustand.docs.pmnd.rs/guides/typescript#bounded-usestore-hook-for-vanilla-stores
6+
function useCreateNotificationStore(
7+
feedClient: Feed,
8+
): UseBoundStore<StoreApi<FeedStoreState>>;
9+
function useCreateNotificationStore<T, U = T>(
10+
feedClient: Feed,
11+
externalSelector: (state: FeedStoreState) => U,
12+
): U;
1113
/**
12-
* Below we do some typing to specify that if a selector is provided,
13-
* the return type will be the type returned by the selector.
14-
*
15-
* This is important because the store state type is not always the same as the
16-
* return type of the selector.
17-
*
14+
* Access a Bounded Store instance
15+
* Allow passing a selector down from useCreateNotificationStore OR useNotificationStore
16+
* We'll favor the the one passed later outside of useCreateNotificationStore instantiation
1817
*/
19-
20-
type StateSelector<T, U> = (state: T) => U;
21-
type FeedStoreStateSelector<T> = StateSelector<FeedStoreState, T>;
22-
23-
// Function overload for when no selector is provided
24-
function useNotificationStore(feedClient: Feed): FeedStoreState;
25-
26-
// Function overload for when a selector is provided
27-
function useNotificationStore<T>(
18+
function useCreateNotificationStore<T, U = T>(
2819
feedClient: Feed,
29-
selector: FeedStoreStateSelector<T>,
30-
): T;
20+
externalSelector?: (state: FeedStoreState) => U,
21+
) {
22+
const store = useStore(feedClient.store);
23+
24+
return (selector?: (state: FeedStoreState) => U) => {
25+
const innerSelector = selector ?? externalSelector;
26+
return innerSelector ? innerSelector(store) : store;
27+
};
28+
}
3129

3230
/**
3331
* A hook used to access content within the notification store.
@@ -43,13 +41,18 @@ function useNotificationStore<T>(
4341
* }));
4442
* ```
4543
*/
44+
function useNotificationStore(
45+
feedClient: Feed,
46+
): UseBoundStore<StoreApi<FeedStoreState>>;
4647
function useNotificationStore<T>(
4748
feedClient: Feed,
48-
selector?: FeedStoreStateSelector<T>,
49-
): T | FeedStoreState {
50-
const useStore = useCreateNotificationStore(feedClient);
51-
const storeState = useStore();
52-
return selector ? selector(storeState) : storeState;
49+
selector: (state: FeedStoreState) => T,
50+
): T;
51+
function useNotificationStore<T, U = T>(
52+
feedClient: Feed,
53+
selector?: (state: FeedStoreState) => U,
54+
) {
55+
return useCreateNotificationStore(feedClient, selector!);
5356
}
5457

5558
export { useCreateNotificationStore };

0 commit comments

Comments
 (0)