react-blips
v0.14.1
Published
Official React bindings for Blips
Downloads
45
Maintainers
Readme
React-Blips
Official React bindings for Blips
Table of contents:
Installation
npm i react-blips
This assumes you are using npm and have already installed blips and graphql
Usage
The Provider
instance
To get started you'll have to
create the blips client
and pass it as a prop to the Provider
component
import { BlipsClient } from 'blips'
import { Provider } from 'react-blips'
// ...
const client = new BlipsClient(...)
ReactDOM.render(
<Provider {...{ client }}>
<App />
</Provider>,
document.getElementById('root')
)
Connect components with graphql
graphql
is the function that creates container components which are connected
to the store.
graphql(...operations [, config])(BaseComponent)
// ...
import { graphql } from 'react-blips'
const TodoList = ({ data: { allTodos = [] } }) => (
<ul className="todo-list">
{allTodos.map(todo => (
<li key={todo.id}>
<label>
<input type="checkbox" checked={todo.completed} />
{todo.label}
</label>
</li>
))}
</ul>
)
const allTodosQuery = `
query allTodosQuery {
allTodos {
id
label
completed
}
}
`
export default graphql(allTodosQuery)(TodoList)
You can add as many operations as you need, the only requirement is that if you
provide a config
object, it must be the last argument.
// ...
const config = { ... }
export default graphql(allTodosQuery, allUsersQuery, config)(TodoList)
By default, the result of your queries will be added to the component's props
under the data
key:
props: {
data: {
allTodos: [...],
allUsers: [...]
}
}
The data
prop
In addition to the fields you query, the data
prop may also contain a
loading
flag and/or an errors
object
loading
Indicates whether or not the operations are still executing.
props: {
data: {
loading: true
}
}
While loading, the fields you've queried may not be part of the data
. Make
sure to add some default values or display a loader until the operations finish
executing.
// default values:
const TodoList = ({ data: { allTodos = [] } }) => (
<ul className="todo-list">{allTodos.map(todo => <li>{todo}</li>)}</ul>
)
// displaying loader while operations is executing
const TodoList = ({ data: { loading, allTodos } }) => {
return loading ? (
<LoadingSpinner />
) : (
<ul className="todo-list">{allTodos.map(todo => <li>{todo}</li>)}</ul>
)
}
error
Contains all the errors that might have resulted from any of the operations.
props: {
data: {
loading: false,
allTodos: [...],
errors: {
allUsers: { ... }
}
}
}
You should always have some sort of error-handling in place, this is generally a good practice, not only when using Blips.
The config
object
graphql
accepts an optional argument which represents the configuration
object. This argument should always be provided last, after all operations.
graphql(...operations, config)(BaseComponent)
config.name
This property allows you to change the name of the data
prop that gets passed
down to your component.
graphql(...operations, { name: 'state' })(BaseComponent)
// props :{
// ...
// state: {
// loading: false,
// allTodos: [...],
// allUsers: [...]
// }
// }
You can also define config.name
as a plain object if you wish to provide
custom names for the other props (queries,
mutations) that graphql
adds to the container component
graphql(...operations, {
name: {
data: 'state',
queries: 'geters',
mutations: 'setters',
},
})(BaseComponent)
// props :{
// ...
// state: { ... },
// getters: { ... },
// setters: { ... }
// }
config.options
This property is an object or function that allows you to provide the variables needed for your operations or to extend the context of the resolvers they will call.
// resolvers.js
const resolvers = {
// ...
Query: {
// returns only todos of the logged user
allTodosQuery: (obj, { first }, { store, user }) => {
store.get('todos', { user_id: user.id }).slice(0, first)
},
},
}
// TodoList.js
const TodoList = ({ data: { allTodos = [] } }) => (
<ul className="todo-list">{allTodos.map(todo => <li>{todo}</li>)}</ul>
)
const allTodosQuery = `
query allTodosQuery($first: Int) {
allTodos(first: $first) {
id
label
completed
}
}
`
export default graphql(allTodosQuery, {
options: {
variables: { first: 10 },
context: { user: localStorage.getItem('currentUser') },
},
})
You can define config.options
as a plain object, or as a function that takes
the component’s props as an argument and returns the object.
export default graphql(allTodosQuery, {
options: props => ({
variables: { first: props.count },
context: { user: props.user },
}),
})
The queries
prop
In addition to data
, the connected component will also receive a queries
prop which contains all the query operations specified, as methods that you can
call manually.
// ...
onUserSelect = async id => {
const { queries: { allTodosQuery, user }, count, user } = this.props
const newUser = await user({ variables: { id } })
const data = await allTodosQuery({ variables: { first: count }, context: { user: newUser } })
this.setState({
todos: data.allTodos,
})
}
// ...
render() {
return (
<div>
<select onChange={e => this.onUserSelect(e.target.value)}>
<option value="600cba27-e8fe-413b-96bc-a9cbf6a2c897">John Doe</option>
<option value="203d7785-14c4-4fa9-8436-b49351f5b6e5">Jane Doe</option>
</select>
<TodoList todos={this.state.todos} />
</div>
)
}
This is great for the above type of behaviour, but you can also use these
queries to poll the state at a specific interval and update the component's
state. The problem with this approach is that you'd have to add the props you
want to poll for to the state so that your component updates correctly. If this
is what you want, you're better off using
subscription
instead of query
The mutations
prop
Another prop passed down to the container component is mutations
. This prop
contains all the mutations you provide to graphql()
class Todos extends Component {
// ...
onKeyUp = e => {
const {
mutations: { createTodoMutation = () => {} },
data: { allTodos = [] },
} = this.props
createTodoMutation({ variables: { label: e.target.value } })
// adds the new todo to the store and our subscription will handle the component update
}
// ...
render() {
const { data: { allTodos = [] } } = this.props
return (
<div>
<input type="text" onKeyUp={this.onKeyUp} />
<TodoList todos={allTodos} />
</div>
)
}
}
const createTodoMutation = `
mutation createTodoMutation(id: String, label: String!, completed: Boolean) {
createTodo {
id
label
completed
}
}
`
export default graphql(allTodosSubscription, createTodoMutation, {
options: {
context: { user: localStorage.getItem('currentUser') },
},
})
Subscribing to state changes
If you provide subscription operations to graphql()
, the connected component
will also subscribe to state changes and will update correctly. It will also
clean up after itself when unmounting. There's no magic happening behind the
scenes, you'll still have to write the resolvers yourself. Read the
Blips documentation about
writing resolvers for subscriptions
const allTodosSubscription = `
subscription allTodosSubscription {
allTodos {
id
label
completed
}
}
`
export default graphql(allTodosSubscription)(TodoList)
The withOperations
HOC
The withOperations
higher-order component creates components that have the
ability to execute queries and mutations manually. The signature of
withOperations
is the same as that of graphql
. It will not provide a data
prop since it won't execute operations for you, but it will provide the
queries
and mutations
props. It's useful for creating components that don't
need to be connected to the store but may still execute mutations or read from
it on demand, like when you have a component a few levels deep from a container
and you don't want to pass callbacks all the way down.
The withClient
HOC
Components created by the withClient
higher-order component have access to the
entire client
instance. withClient
accepts a single argument, the base
component and it will provide a client
prop. Messing with the client instance
directly is risky, so this is not a recommended approach, but it's available if
you consider that you need it. Event then, withOperations
may still be enough.
Tips
See Blips documentation to read about tips