Comments (18)
Edit:
Since v3.1.4 a build-in middleware has been added, see this section of the readme for more info.
I made a pretty basic one, but it does the job.
import create, { GetState, SetState, StateCreator, StoreApi } from "zustand";
type Product = { id: number; amount: number };
interface Store {
products: Product[];
setProducts: (payload: Product) => void;
}
const isRunningInBrowser = () => typeof window !== "undefined";
const persist = <T>(name: string, config: StateCreator<T>) => (
set: SetState<T>,
get: GetState<T>,
api: StoreApi<T>,
): T => {
const state = config(
(payload) => {
set(payload);
if (isRunningInBrowser) {
localStorage.setItem(name, JSON.stringify(payload));
}
},
get,
api,
);
return {
...state,
...(isRunningInBrowser() && JSON.parse(localStorage.getItem(name))),
};
};
export const [useShoppingCart] = create<Store>(
persist<Store>("shoppingCart", (set, get) => ({
products: [],
setProducts: (payload) => {
const state = get();
set({ ...state, products: [...state.products, payload] });
},
})),
);
The persist
middleware will automatically rehydrate your state. No need to do anything specific in your state creator function 🎉
In my example everything is in one file, but you can (and I do) put the persist
and isRunningInBrowser
functions in a specific file, then import it whenever you need to use a persistent store.
And it's also Typescript friendly.
from zustand.
What about this?
import create from 'zustand';
const persist = config => (set, get, api) => {
const initialState = config(
args => {
set(args);
window.localStorage.setItem('state', JSON.stringify(get()));
},
get,
api,
);
const restoredState =
typeof window === 'undefined'
? {}
: JSON.parse(localStorage.getItem('state'));
return {
...initialState,
...restoredState,
};
};
const [useStore] = create(
persist(set => ({
theme: 'light',
acceptedCookies: null,
setTheme: theme => set(state => ({ theme })),
})),
);
export default useStore;
should be SSR friendly and work?
from zustand.
@marcoacierno using your example and I still get a console error Text content did not match. Server: "1 count" Client: "3 count"
. Using Next.js
from zustand.
On an electron app, I recently use this as a persist process.
const MainLayout : React.FunctionComponent = () => {
const [ initialized, setInitialized ] = useState<boolean>(false);
useEffect(() => {
const initializedSettings: ReducerEffect = apiSettings.getState().reducers.initialize;
const initializedThemes: ReducerEffect = apiThemes.getState().reducers.initialize;
// Initialized the app with the persists states when the app is launched
const unsubInitialData = myIpcRenderer.on('APP_INITIAL_STATE', data => {
initializedSettings(data.initialSettings);
initializedThemes(data.initialThemes);
setInitialized(true);
});
// Each time the state that you want to persist is changed, the subscribe api trigger your persist function (like storing in AsyncStorage)
const unsubSaveSettings = apiSettings.subscribe(
(state: SettingsState) => {
if (initialized)
myIpcRenderer.send('APP_SAVE_SETTINGS', state);
},
settingsState => settingsState.state
);
const unsubSaveThemes = apiThemes.subscribe(
(state: ThemesState) => {
if (initialized)
myIpcRenderer.send('APP_SAVE_THEMES', state);
},
themesState => themesState.state
);
return () => {
unsubInitialData();
unsubSaveSettings();
unsubSaveThemes();
}
}, [initialized]);
return (
...
);
}
I used this structure for my zustand states:
{
state: {...}, // state values
reducers: {...}, // pure functions to change the state
effects: {...} // async functions with side effects
}
You can easily think of a web/mobile way with that example. It's not a middleware, but the subscribe api is perfect for this use.
from zustand.
@drcmda having persistence as option would be great really. supporting it in ssr indeed would be too specific case, but persistence of a store is quite common problem really.
from zustand.
Mine was/is a very basic app, but that's what I implemented:
import create from 'zustand';
const restoredState = JSON.parse(localStorage.getItem('state'));
const persist = config => (set, get, api) =>
config(
args => {
set(args);
window.localStorage.setItem('state', JSON.stringify(get()));
},
get,
api,
);
const [useStore] = create(
persist(set => ({
theme: 'light',
...restoredState,
setTheme: theme => set(state => ({ theme })),
})),
);
export default useStore;
where I basically first set the default state of each variable and then overwrite them with what was inside the store.
It won't work well if you change type of information, or anything else really but gets the job done (at least for me)
from zustand.
FWIW, my initial approach is terrible for SSR. It took me 6 hours to figure it out.
Apparently, this:
const isBrowser = typeof window !== "undefined"
const persistedState =
isBrowser
? JSON.parse(localStorage.getItem("sidebarState"));
: null
doesn't work well with SSR.
The approach I need to take is to give the original state inside create
function the initial state, and then use useMount
to get localStorage
data and update state.
Just in case anyone may run into this in the future!
from zustand.
is this something we can add officially to the lib? or is it too specific?
from zustand.
@drcmda I think this is too specific. I don't think there is a way for zustand
to support it right out of the box. But maybe some documentation would be beneficial? Although this is not zustand
-specific as it's a problem with other state managements too.
@marcoacierno I don't think that would work, although I haven't tested it yet. The problem is that even if the store
state is updated with persisted data, the component will not rerender.
What I needed to do:
function Component() {
const { state, setState } = useStore()
React.useEffect(() => {
if (typeof window !== "undefined") {
setState(JSON.parse(localStorage.getItem("key")))
}
}, [setState])
return <Stuff />
}
from zustand.
I'm not familiar with SSR, not sure how to do it.
But I created a package https://www.npmjs.com/package/zustand-persist for react and react native
Contributions are welcome
from zustand.
@AnatoleLucet how would you implement your solution with AsyncStorage (react-native - which is async) ?
from zustand.
Hey @GaspardC, sorry I forgot to respond 🙃
I can't see how this could be implemented with an async storage without making every function async (which would require you to get your store's data in a useEffect or something) or falling in a callback hell.
Though I think this might be doable with a custom hook. But I'm not sure how a wrapper hook could adapt to any kind of zustand store.
Maybe someone else will have an idea 🤷♂️
from zustand.
I get the error Text content did not match. Server: "1 count" Client: "3 count"
when using persist along with Next.js.
@maxwaiyaki were you able to solve this?
from zustand.
This comment helped me solve it: #324 (comment)
from zustand.
This comment helped me solve it: #324 (comment)
do you mind to share what does it solved for you, because I had removed it due to same effects depsite without ssr it and just stick back to one in the documentation?
from zustand.
This comment helped me solve it: #324 (comment)
do you mind to share what does it solved for you, because I had removed it due to same effects depsite without ssr it and just stick back to one in the documentation?
I don't get the error Text content did not match. Server: "x count" Client: "y count"
anymore, after wrapping in noSsr component.
What did you use from the documentation to solve it?
from zustand.
Same issue with next 13.4.4.
from zustand.
This comment helped me solve it: #324 (comment)
do you mind to share what does it solved for you, because I had removed it due to same effects depsite without ssr it and just stick back to one in the documentation?
I don't get the error
Text content did not match. Server: "x count" Client: "y count"
anymore, after wrapping in noSsr component. What did you use from the documentation to solve it?
just this exactly same https://docs.pmnd.rs/zustand/recipes/recipes#persist-middleware
from zustand.
Related Issues (20)
- Argument of type '(state: ClientStore) => Client | undefined' is not assignable to parameter of type 'ClientStore | Partial<ClientStore> | ((state: ClientStore) => ClientStore | Partial<ClientStore>)'. Type '(state: ClientStore) => Client | undefined' is not assignable to type '(state: ClientStore) => ClientStore | Partial<ClientStore>'. Type 'Client | undefined' is not assignable to type 'ClientStore | Partial<ClientStore>'. Type 'undefined' is not assignable to type 'ClientStore | Partial<ClientStore>'.ts(2345)
- The value I save to storage with persist is delayed when the page refreshes.
- State updated in one component but not in another one
- Typescript + Slices = trouble HOT 7
- Setting nested state make the other states change (their values didn't change)
- zustand concurrent update issue
- Typo in GitHub "About" HOT 1
- import { create ,WritableDraft} from "zustand" HOT 1
- Introduction Zustand page a documentation typo HOT 1
- Ts error on type annotation HOT 3
- Persist middleware keeping old versions of functions around. Breaking apps when deploying to prod
- devtools with option `serialize.immutable` does not deserialize state from devtools correctly (broken redux devtools "Jump" / "Revert" etc.)
- state empty on first render HOT 1
- onRehydrateStorage is invoked before storeage.getItem HOT 1
- About API
- About api docs
- Documentation incorrect: Usage with React Contexts HOT 2
- "useEffect" is not exported by "node_modules/react/index.js"
- Usage of import.meta.env
- enums not working HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from zustand.