-
-
Save koenbok/ae7b94f9fefccc16a34589af344db789 to your computer and use it in GitHub Desktop.
import * as React from "react"; | |
/** | |
A hook to simply use state between components | |
Warning: this only works with function components (like any hook) | |
Usage: | |
// You can put this in an central file and import it too | |
const useStore = createStore({ count: 0 }) | |
// And this is how you use it from any component | |
export function Example() { | |
const [store, setStore] = useStore() | |
const updateCount = () => setStore({ count: store.count + 1 }) | |
return <div onClick={updateCount}>{store.count}</div> | |
} | |
*/ | |
export function createStore<T>(state: T) { | |
// Store the initial state, copy the object if it's an object | |
let storeState: T = typeof state === "object" ? { ...state } : state | |
// Keep a list of all the listener, in the form of React hook setters | |
const storeSetters = new Set<Function>() | |
// Create a set function that updates all the listeners / setters | |
const setStoreState = (state: Partial<T>) => { | |
// If the state is an object, make sure we copy it | |
storeState = | |
typeof state === "object" ? { ...storeState, ...state } : state | |
// Update all the listeners / setters with the new value | |
storeSetters.forEach((setter) => setter(storeState)) | |
} | |
// Create the actual hook based on everything above | |
function useStore(): [T, typeof setStoreState] { | |
// Create the hook we are going to use as a listener | |
const [state, setState] = React.useState(storeState) | |
// If we unmount the component using this hook, we need to remove the listener | |
React.useEffect(() => () => storeSetters.delete(setState), []) | |
// But right now, we need to add the listener | |
storeSetters.add(setState) | |
// Return the state and a function to update the central store | |
return [state, setStoreState] | |
} | |
return useStore | |
} |
If I use the store.tsx in Framer (2020.31), it gives an error with
setter
(line 23)(parameter) setter: unknown This expression is not callable. Type '{}' has no call signatures.(2349)
How can I fix this?
Try changing the second line in the function to:
const storeSetters = new Set<(state: T) => void>()
For an easy copy/paste:
import * as React from "react"
import { useState, useEffect } from "react"
/*
A hook to simply use state between components
Usage:
// You can put this in an central file and import it too
const useStore = createStore({ count: 0 })
// And this is how you use it from any component
export function Example() {
const [store, setStore] = useStore()
const updateCount = () => setStore({ count: store.count + 1 })
return <div onClick={updateCount}>{store.count}</div>
}
*/
export function createStore<T>(state: T) {
let storeState: T = Object.assign({}, state)
const storeSetters = new Set<(state: T) => void>()
const setStoreState = (state: Partial<T>) => {
storeState = Object.assign({}, storeState, state)
storeSetters.forEach((setter) => setter(storeState))
}
function useStore(): [T, typeof setStoreState] {
const [state, setState] = useState(storeState)
useEffect(() => () => storeSetters.delete(setState), [])
storeSetters.add(setState)
return [state, setStoreState]
}
return useStore
}
Just tried this now, awesome work @koenbok. I'm using createStore with a navigation code component and an overridden Framer Page component to easily emulate the navigation in a mobile app with custom effects etc.
This is so useful, I'm wondering if you should promote it more or ship it as part of Framer 💯 😃
@koenbok What's the difference between this and the Data()
function already available in Framer?
Not Koen but: as far as I understand the Data()
function only works between overrides. This is (potentially) shared across all overrides and components in a project.
@jacopocolo How would the code look like if you use this to share data between two different components that both have their own file? I do not understand where you initialise the store and the value you want to share.
For instance, I have these components:
- Slider.tsx
- Dial.tsx
I want to share a numeric value between the slider and the dial, where do I define the store and where do I initialise this value?
Many thanks in advance!
@DvDriel first of all you'd want to set up your store and impot like this: https://gist.github.com/koenbok/ae7b94f9fefccc16a34589af344db789#gistcomment-3214773
Then in your components you'd add:
const [store, setStore] = useStore()
This allows you to read the store as a regular variable {store.var}
. Or to write a variable with the setStore hook setStore({varName: value})
.
The store can contain a single variable or a set of objects.
@koenbok Would this also work in Framer Web? I tried to insert the code but get an error:
It seems like TypeScript got a bit stricter with warnings, but it should totally work.
Great, got it working indeed. What would be the right way to get this working on a class?
EDIT: nvm, figured out it was preferably to convert old class to a function. got it working now.
If I use the store.tsx in Framer (2020.31), it gives an error with
setter
(line 23)How can I fix this?