Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

context support #5

Open
bmcmahen opened this issue Apr 2, 2019 · 15 comments
Open

context support #5

bmcmahen opened this issue Apr 2, 2019 · 15 comments

Comments

@bmcmahen
Copy link
Owner

bmcmahen commented Apr 2, 2019

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).

@georgefeast
Copy link

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 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!

@bmcmahen
Copy link
Owner Author

bmcmahen commented Apr 2, 2019

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.

@georgefeast
Copy link

Ah okay yeah I was also getting this error

Screen Shot 2019-04-03 at 8 54 43 AM

Is this related to the same issue? Am I meant to set container somehow or pass the ref through?

@bmcmahen
Copy link
Owner Author

bmcmahen commented Apr 3, 2019

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.

@pashazz
Copy link

pashazz commented May 8, 2019

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...

@bmcmahen
Copy link
Owner Author

bmcmahen commented May 8, 2019

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.

@pashazz
Copy link

pashazz commented May 8, 2019

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

@bmcmahen
Copy link
Owner Author

bmcmahen commented May 8, 2019

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.

@pashazz
Copy link

pashazz commented May 8, 2019

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

@iampava
Copy link

iampava commented Oct 17, 2020

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.

@victorocna
Copy link

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.

@bmcmahen
Copy link
Owner Author

hey all, I don't really have time to work on this currently. Happy to accept a PR 👍

@victorocna
Copy link

Hello @bmcmahen! I will have a look at it in the next couple of days. Cheers 👍

@victorocna
Copy link

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?

@bmcmahen
Copy link
Owner Author

bmcmahen commented Feb 8, 2021

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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants