Open nirvdrum opened 2 years ago
Hi @nirvdrum. Thanks for your feedback.
I'm having trouble understanding the use case and the specific issue with 3rd party context. Would it be possible to provide an example to help me understand it? A PR would certainly be welcome as it could contain an example SB story that shows it in action.
Hi @tyom. Thanks for taking a look. This is my first Storybook project, so apologies in advance if I've got something wrong.
To make the example concrete, I'm using @auth0/auth0-react to handle authentication for my app. This library ships with an Auth0Context
and components consume it using a useAuth0
hook. As an example, a call might look like:
const { isAuthenticated, isLoading, loginWithRedirect } = useAuth0();
The definition of useAuth0
is:
const useAuth0 = <TUser extends User = User>(): Auth0ContextInterface<TUser> =>
useContext(Auth0Context) as Auth0ContextInterface<TUser>;
As you can see, useAuth0
is a simple wrapper around React's useContext
, supplying the Auth0 context object. That's the one I need to mock to simulate a logged-in user.
My attempt at using the add-on looked like:
export const Default = Template.bind({});
Default.decorators = [withReactContext({
context: Auth0Context,
initialState: {
isAuthenticated: true,
isLoading: false,
getAccessTokenSilently: () => new Promise((resolve) => resolve('My JWT')),
user: {
sub: "auth0|some_auth0_id",
name: "John Doe"
},
}
})];
So, I set the initialState
value to an object that conforms to the type the context provides. As far as I can tell, storybook-react-context assumes the context value is going to be a tuple of (currentValue, setValue)
by way of wrapping the object in a useReducer
call. It changes the type of the context value to match that assumption. Consequently, using the add-on to mock out the Auth0Provider
creates a situation where the useAuth0
call adds an additional level of indirection. The call would need to be something like:
const { isAuthenticated, isLoading, loginWithRedirect } = useAuth0()[0];
While I could wrap the Auth0Context
in a custom component and try to handle the change this add-on is making, I can't change the 3rd party context provider to conform. Unless I've managed to use the add-on wrong, I can't see a way to make this work. Naively, it looks to me like the add-on should not assume a reducer is being used and make the caller supply the two reducer parts if one is to be used.
To solve the problem in the meanwhile, I took the spirit of this add-on and wrote my own decorator, added to preview.tsx:
export const decorators = [
(Component: React.ComponentType<unknown>) => {
const initialValue = useAuth0();
return (
<Auth0Context.Provider
value={{
...initialValue,
isAuthenticated: true,
isLoading: false,
getAccessTokenSilently: () => new Promise((resolve) => resolve('My JWT')),
user: {
sub: 'auth0|some_auth0_id',
name: 'John Doe'
}
}}
>
<Component />
</Auth0Context.Provider>
);
}
];
Please let me know if you need any additional information.
Hi @nirvdrum. Apologies for the radio silence. I've just done some housekeeping in the library and also looked into the use case you describe here. There is now a new option in the storybook-react-context
decorator called useReducer
which is a boolean, and when it's set to false
it will not use React's useReducer
in the context provider and pass the initialState
directly to its value. I've added this example to the lib's Storybook examples. I hope it helps to solve the problem you're facing.
Try to upgrade to version 0.5.0 to get this functionality.
Let me know if that helps or if I misinterpreted your use case.
This only helps with the initial value. However, if context is more than just a single object of primitive values, it doesn't help. For example, many providers export a {value, setValue}
as an internal useState
.
useReducer: false will allow the value
to be set, but it completely removes the rest of the interface from the context, and so setValue
is no longer a function, and that means I can't change contexts in my stories
Could you help setting up a test case for this, @LongLiveCHIEF?
Simple example:
const ColorContext = React.createContext()
interface ColorThemeContext {
background: Color;
border: Color;
}
const ColorProvider({children}){
const [context, setContext] = useState<ColorThemeContext>()
return <ColorContext.Provider value={{context, setContext}}>{children}</ColorContext.Provider>
}
The idea would be that inside the story we'd want to use a useContext
hook:
export const SomeStory = () => {
const { context, setContext } = useContext(ColorContext)
//.... some story jsx
}
The problem is that currently when i decorate with this addon, the context
has the correct value from initialState
, but setContext
has been stripped from the ColorContext
context object entirely (along with any other properties)
granted, that may be outside the use case of this addon.
Maybe we could add a merge
or deepMerge
option or something like that.
The use case for this addon when I initially created was to tap into the component's context in Storybook and change it at will. Now I realise that the use case is not even properly presented in the examples I have. I need to review those use cases and present them better while looking into making sure that the provider value is not assumed to be a tuple from useReducer
. For example, a use case for Auth0 context, as outlined in this issue. I'll post an update here when I had a chance to do that. Thanks.
I've refactored the storybook-react-context
(v0.6.0) package which now has useProviderValue
parameter. The value return from the call of this function is set as the value of the context provider. This should give the necessary tools to customise the context in a way that is described in this issue.
Please note that this is a breaking change. The useReducer
parameter has been removed and context
renamed to Context
to make it clearer that this is React's context rather than story context. See the updated example stories for the updated use cases.
Let me know if it solves your issue.
Hi @nirvdrum. It's been a long time, and I neglected this package. I have just rewritten it and am planning to release a new version. If you want to learn more, please see this comment for details. Cheers.
The storybook-react-context addon repacks the
initialState
value into a new type that doesn't necessarily match the original context provider value. The addon unconditionally changes the provider value to aReact.useReducer
result. It looks like the implementation may overfit to a particular use case. I'm working with a provider where the value is just a plain object. What I pass as theinitialState
value should be what the component gets with aReact.useContext
call. In this particular case, it's a 3rd party context so I don't have the liberty of adjusting it to conform to this addon's expectations.I'm happy to pull together a PR, but I'd like to get agreement on what, if anything, should change.