unistore icon indicating copy to clipboard operation
unistore copied to clipboard

How to "dynamically" `connect` to different states with React/Preact?

Open rmorse opened this issue 4 years ago • 1 comments

I see that connect can be used like this:

connect( [ 'var1', 'var2' ], actions )

Which works well... however I want to init a component, and pass the state name into it somehow...

The problem: I have a store, which based on user interaction (from the dom) will create a new state object eg state.field_${id}, and then dynamically render a Preact component... I want to pass down the state name / ID into a preact component/wrapper so it can connect to its own state object - field_${id},

The only thing I can think of, is to just connect it to the whole state, pass in the ID, and then filter that out manually...

const allState = ( state ) => {
	return state;
};
export const MyComponent = connect(
	allState,
	actions
)( ( props ) => {
        const myState = props[ `field_${ props.id }` ];
	return <>...</>;
} );

But it doesn't sit right with me, and the component would naturally re-render on any state change rather the part we're after... .

Is this possible / are there any other approaches I might have missed?

Thanks!

rmorse avatar Jun 11 '21 11:06 rmorse

Ok so I might have answered my own question.

I had a peek at the connect function and stole some ideas from there.

What I've done:

  1. Create a new context (the context is not exposed in unistore/preact) and ensure to pass the store as a prop to the provider
  2. Wrap this around the app just like unistore/preact
  3. Then use the useContext hook to supply the store to the component
  4. Update local state to keep track of the updates and trigger re-renders
  5. Wrap as a HOC

const StoreContext = createContext( {} );
// Make sure a StoreContext Provider is around the app

const getStateByKey = ( state, key ) => {
	return state[ key ] ?? null;
};

const withStoreKey = ( WrappedComponent ) => ( { storeKey, ...rest } ) => {
	const store = useContext( StoreContext );
	const [ localState, setLocalState ] = useState(
		getStateByKey( store.getState(), storeKey )
	);
	const receiveState = ( state ) => {
		setLocalState( getStateByKey( state, storeKey ) );
	};
	useEffect( () => {
		store.subscribe( receiveState );
		return () => {
			store.unsubscribe( receiveState );
		};
	}, [] );

	if ( localState === null ) {
		return null;
	}
	return ( <WrappedComponent { ...localState } { ...rest } /> );
};

So now I can do:

const MyComponent = () => {
	return <div>Hello wrld</div>;
};
const MyConnectedComponent = withStoreKey( MyComponent );
const App = () => {
	return <MyConnectedComponent storeKey={ 'customStoreKey' } more={ 'props' } />;
};

Using this method, I don't need to import unistore/preact and can designate specific props from a store's state to specific components...

Open to any suggestions of doing this another way but I think I'm quite happy with this implementation :)

rmorse avatar Jun 11 '21 18:06 rmorse