toasted-notes
toasted-notes copied to clipboard
context support
Given the current API, jsx in render props or elements won't get access to context. Explore ways to support context while maintaining the current API, and inform users of ways to supply context to their render callback (wrap it with context providers).
I think I ran into this problem today.. Tried to replicate how your toasts work in Sancho UI with my own render callback component + toasted-notes where <ANotif/> is a custom component for displaying the notification.
toaster.notify( ({ onClose, id }) => ( <Fragment> <GlobalStyle /> <div> <ANotif intent={intent} message={message} handleNotifClosed={onClose} /> </div> </Fragment> ), options )
It works as long as ANotif contains just a message with a div but breaks as soon as I add styled-components inside. Is this what you're referring to in this issue / do you know any workaround? Sancho and toasted-notes are great by the way!
Hmm yeah, it'll break if you rely on theme variables with styled components. The key is to wrap your render callback with your theme, like so:
function CustomAlert() {
<div>Custom</div>
}
const CustomAlertWithTheme = withTheme(CustomAlert)
toast.notify(() => <CustomAlertWithTheme />
I think one possible solution on our end (and something I've been a bit reluctant to do) is to provide a ToastedNotesContainer
component which provides a target render element inside of your React component tree, thus ensuring that theme context is passed to the render callback.
Ah okay yeah I was also getting this error
Is this related to the same issue? Am I meant to set container somehow or pass the ref through?
Huh, that's odd. I don't think this is related. It's indicating that the ref isn't being set for some reason and I'm not sure why that'd be the case. The ref should be referring to the <animated.div />
container element which should definitely be there. Check here.
Hello! Bumping this thing up.
I wasn't able to use context properly. I want to use Apollo client inside a toast (this is GraphQL thing) so here's what I'm doing:
export const taskComponent = (): React.ComponentType<{}> => {
return withApollo(({client}: ClientProps) => {
return <ApolloProvider client={client}>
<Subscription subscription={TaskInfoUpdateDocument}
variables={{}}>
{({data}) => {
return (<div>{JSON.stringify(data)}</div>);
}}
</Subscription>
</ApolloProvider>
});
};
Now when I try to use this thing in my component tree, I use something like:
export const showTaskInfo = (TaskComponent: React.ComponentType<{}>) => {
toast.notify(() => <TaskComponent/>);
};
When I call showTaskInfo(taskComponent()), it does not work since withApollo requires a root div to be wrapped into a context provider named ApolloProvider.
How can I wrap a notification root div in an ApolloProvider?
I've been trying to do it like this...
//This is my root app render
<LanguageProvider messages={messages}>
<ConnectedRouter history={history}>
<ApolloProvider client={client}>
<Suspense fallback={<div>Loading...</div>}>
<div id="react-toast">
</div>
<App/>
</Suspense>
</ApolloProvider>
</ConnectedRouter>
</LanguageProvider>
but it still creates its own div with id react-toast
...
Hey @pashazz! For starters, I'd probably recommend not doing any grapqhl fetching within the toasted-notes
callback itself. I'd fetch any relevant information within the component that is making the call to toasted-notes instead.
Having said that, you should be able to provide the ApolloProvider
within the render callback like this:
import client from './path/to/client';
const Notification = () => (
<ApolloProvider client={client}>
{/* subscriptions or queries go here */}
</ApolloProvider>
);
toast.notify(<Notification />);
I haven't actually tried this so your mileage may vary.
I think one solution I might build to better support context is to provide an optional <ToastedNotesProvider />
component which effectively provides a target for toast notifications to render into.
Thank you for your help. I've just done my GraphQL fetching that way:
const observable = client.subscribe({
query: TaskInfoUpdateDocument,
variables: {
ref: taskId,
}
});
observable.subscribe(({data}) => {
const func = async () => {
client.cache.writeFragment<TaskDataType>({
fragment: TaskFragmentFragmentDoc,
id: dataIdFromObject(data.task),
data: await getTask(data.task),
fragmentName: "TaskFragment",
});
};
func();
toast.notify(({onClose, id}) => {
const data = client.cache.readFragment<TaskFragmentFragment>({
id: dataIdFromObject({
__typename: "GTask",
ref: taskId,
}),
fragment: TaskFragmentFragmentDoc,
fragmentName: "TaskFragment",
});
let alertColor = 'info';
if (data)
switch (data.status) {
case TaskStatus.Cancelled:
case TaskStatus.Cancelling:
alertColor = 'warning';
break;
case TaskStatus.Failure:
alertColor = 'danger';
break;
case TaskStatus.Pending:
alertColor = 'info';
break;
case TaskStatus.Success:
alertColor = 'success';
}
return (
<div>
<Alert color={alertColor} toggle={onClose}>
<h4 className="alert-heading">
{data && data.nameLabel || title}
</h4>
<Progress value={data && data.progress * 100 || 0}>
</Progress>
<hr/>
{data && data.errorInfo.map(string => (
<Label>{string}</Label>
))}
</Alert>}
</div>
)
}
},
{
duration: null,
})
};
My problem is that the div in here does not re-render when data in cache changes, except for the moment when it closes (when duration is not null, it actually rerenders just before closing). Any thoughts on how I can force rerender? This fetching code works just fine on a plain Component
Yeah, I don't think the readFragment
performs updates when your cache actually changes.
The subscribe
function should be called when your cache changes, I think. So you could update component state when the subscribe callback is called, and then have your components update in response to those state changes.
Buuut, I'm honestly not sure. I've probably try the <ApolloProvider />
approach that I mentioned above. I can maybe hack together an example later tonight if I have some time.
What do you mean exactly? I intend to update the UI of notification when my subscription updates. I use cache for that. If only I had a method to trigger an update of my notification UI from subscribe callback, It'd resolve my problems. I can probably call notify from subscribe callback but then Id have had many notification. Now if I could call notify that I replace an existing notification using some ID that I can provide, that'd be great
Hey! Any chance we'll get first-hand Context support? Without the need to wrap the component?
I'd be more than happy to contribute to this with a PR.
I second this. First-hand context support would be awesome. It's the missing piece for me since I use this package with some translatable messages.
hey all, I don't really have time to work on this currently. Happy to accept a PR 👍
Hello @bmcmahen! I will have a look at it in the next couple of days. Cheers 👍
Hello everyone. I had a look at it last night and have some questions. As far as I can tell, the context is missing because the ToastManager
component is not placed in the app, like <div><ToastManager /></div>
, but created at the root level like so. Would you like a PR that will add a parent component for toasts that can be placed in the app? How about breaking changes, should these be avoided?
That makes sense to me. I imagine you'd want something like ToastProvider
which can be placed somewhere in the react tree. If toast context is provided, it'd render using the toast provider... otherwise (to avoid breaking changes), you'd just render toasts as they currently are. I've actually implemented a similar system internally for work and it's worked well for us.
<StyledComponentContextProvider>
<ToastProvider>
<App />
</ToastProvider>
</StyledComponentContextProvider>