Blueprint: [Core/Toast] Toaster.create() returns null

Created on 7 Oct 2017  路  10Comments  路  Source: palantir/blueprint


Bug report

  • __Package version(s)__: @blueprintjs/core": "^1.30.0"
  • __Browser and OS versions__: Chrome Canary | MacOS 10.12.6

Steps to reproduce

  1. Create a toaster
  2. Try to show it
  3. Fails as toaster is null

Actual behavior

Toast does not show

Expected behavior

Toast to show

core needs more info

Most helpful comment

Oh it's also worth noting that calling Toaster.create inside a component lifecycle method can actually return null (and it _definitely_ returns null in React 16): https://reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes (2nd bullet)

_Edit:_ All this to support my proposal of defining a Toaster at initialization time (i.e., import) instead of at render() or mount time.

All 10 comments

Please provide code examples that repros and exact error messages

@llorca apologies for the delay.

The defined method

import { Position, Toaster } from '@blueprintjs/core'

export const appNotification = dismissAction => Toaster.create({
  className: 'app-toaster',
  position: Position.TOP,
  onDismiss: () => dismissAction()
})

The component

import React, { Component } from 'react'
import PropTypes from 'prop-types'

import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import { appNotification } from './utils/notification'

import Header from './components/Header'
import Footer from './components/Footer'

import { clearNotification } from './actions/app'

class Layout extends Component {
  constructor(props) {
    super(props)
    this.state = {
      toaster: null
    }
  }

  componentDidMount() {
    this.setState = {
      toaster: appNotification(this.props.actions.clearNotification)
    }
  }

  componentWillReceiveProps(nextProps) {
    const { toaster } = this.state
    const { app: { notification } } = this.props
    const { app: { notification: nextNotification } } = nextProps
    if (notification !== nextNotification) {
      toaster && toaster.show(nextNotification)
    }
  }

  render() {
    const { app } = this.props
    return (
      <div>
        <Header app={app} />
        <div>
          {this.props.children}
        </div>
        <Footer />
      </div>
    )
  }
}

const mapStateToProps = state => ({
  app: state.app
})

const mapDispatchToProps = dispatch => ({
  actions: bindActionCreators({ clearNotification }, dispatch)
})

Layout.propTypes = {
  children: PropTypes.any.isRequired,
  app: PropTypes.object.isRequired,
  actions: PropTypes.shape({
    clearNotification: PropTypes.func
  })
}

export default connect(mapStateToProps, mapDispatchToProps)(Layout)

appNotification is always null, o calling .show(message) on it breaks ...

cannot call method show on null

this.setState = { should be this.setState({, no?

^ @casoetan - yes, try this and let us know if it fixes your issue.

@casoetan there are a number of bugs in your code, including the setState assignment mentioned above. The most glaring is, of course, that you're not using the Toaster API correctly:
Toaster.create() returns a toaster _instance_ with a show method. It is this show method which accepts onDismiss, so you're passing that handler in the wrong place.

The expected usage is something like this:

  1. Create a Toaster instance using Toaster.create() (typically one instance per position).

    • I like defining this instance in a separate module like common/toaster.ts so any component can simply import it and profit 馃捀

      js export const TopToaster = Toaster.create({ position: TOP, ... })

  2. Call instance.show({ ...props, onDismiss }) to show a toast in that Toaster.
    ```js
    import { TopToaster } from "../path/to/common/toaster";
// later, in response to user event:
TopToaster.show({ ...props, onDismiss: this.handleDismiss })
```

Hope this helps! Please refactor your code and let us know if you can get it working.

Oh it's also worth noting that calling Toaster.create inside a component lifecycle method can actually return null (and it _definitely_ returns null in React 16): https://reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes (2nd bullet)

_Edit:_ All this to support my proposal of defining a Toaster at initialization time (i.e., import) instead of at render() or mount time.

@AlexLandau the set state was obviously from scratching my head for over 2 days trying to get what worked on React 15 to work on React 16.

@giladgray you are quite right. In React 16, this would always return null. #https://github.com/facebook/react/issues/10309#issuecomment-318433235.

The Toaster does not work with SSR as it depends on the dom, reason I'm instantiating in ComponentDidMount.

@AlexLandau the set state was obviously from scratching my head for over 2 days trying to get what worked on React 15 to work on React 16.

@giladgray you are quite right. In React 16, this would always return null. #https://github.com/facebook/react/issues/10309#issuecomment-318433235.

The Toaster does not work with SSR as it depends on the dom, reason I'm instantiating in ComponentDidMount.

Solved with https://github.com/palantir/blueprint/issues/1205

export const Toast = (typeof window !== 'undefined')
   ? Toaster.create({
     className: 'my-toaster',
     position: Position.BOTTOM_RIGHT
   })
  : null

I am using a DIC for the toaster, so it calls it whenever its used for the first time.

In this case its also returning null.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NickyYo picture NickyYo  路  18Comments

Hexxeh picture Hexxeh  路  18Comments

ripitrust picture ripitrust  路  19Comments

dmackerman picture dmackerman  路  46Comments

vladeck picture vladeck  路  32Comments