Last active
November 9, 2018 17:41
-
-
Save ryanflorence/c379f7250e810b839d5c2080c39c2724 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import firebase from 'firebase/app' | |
import 'firebase/database' | |
const VERSION = 'v1' | |
const config = { | |
apiKey: '', | |
authDomain: '', | |
databaseURL: '', | |
storageBucket: '', | |
messagingSenderId: '' | |
} | |
firebase.initializeApp(config) | |
export const db = firebase.database() | |
export const getPath = (path) => `${VERSION}/${path}` | |
export const getRef = (path) => db.ref(getPath(path)) | |
export const listToArray = (obj) => ( | |
Object.keys(obj || {}).map((key) => ( | |
{ ...obj[key], key } | |
)) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { PropTypes, Component } from 'react' | |
import { getRef, listToArray } from '../utils/firebase' | |
import { ErrorMessage, Loading } from '../Theme' | |
/* | |
```js | |
<Ref path="/somewhere"> | |
{({ error, loaded, value }) => ( | |
// `value` is an object w/ keys | |
// `error` is an error thrown by firebase | |
// `loaded` is whether or not the value has loaded at least once | |
// (can't just check `value === null` value can be null!) | |
)} | |
</Ref> | |
<Ref list={true} path="/somewhere"> | |
{({ value }) => ( | |
// `value` - will be an array with an added `key` property on each item | |
// when you add the `list={true}` prop | |
)} | |
</Ref> | |
<Ref valueOnly={true} path="/somewhere"> | |
{(value) => ( | |
// now instead of an object of all states (error, loaded, value) | |
// it only calls back when it's loaded so you can skip loaded checks, | |
// it'll render <Loading/> and <ErrorMessage/> for you | |
)} | |
</Ref> | |
<Ref | |
path="/somewhere" | |
query={(ref) => ref.limitToLast(50)} | |
/> | |
``` | |
*/ | |
class Ref extends Component { | |
static propTypes = { | |
path: PropTypes.string, | |
list: PropTypes.bool, | |
query: PropTypes.func, | |
children: PropTypes.func, | |
valueOnly: PropTypes.bool | |
} | |
static defaultProps = { | |
list: false, | |
query: (ref) => ref, | |
valueOnly: false | |
} | |
state = { | |
loaded: false, | |
value: null, | |
error: null | |
} | |
unmounted = false | |
componentDidMount() { | |
this.subscribe() | |
} | |
subscribe(props) { | |
props = props || this.props | |
const { path, list, query } = props | |
this.ref = query(getRef(path)) | |
this.ref.on('value', (snapshot) => { | |
if (!this.unmounted) { | |
const value = snapshot.val() | |
this.setState({ | |
value: list ? listToArray(value) : value, | |
loaded: true | |
}) | |
} | |
}, (error) => { | |
if (!this.unmounted) { | |
this.setState({ error, loaded: true }) | |
} | |
}) | |
} | |
componentWillReceiveProps(nextProps) { | |
if (nextProps.path !== this.props.path) { | |
this.setState({ value: null, error: null, loaded: false }) | |
this.unsubscribe() | |
this.subscribe(nextProps) | |
} | |
} | |
unsubscribe() { | |
this.ref.off() | |
} | |
componentWillUnmount() { | |
this.unsubscribe() | |
this.unmounted = true | |
} | |
render() { | |
const { children, valueOnly } = this.props | |
const { error, value, loaded } = this.state | |
return children ? ( | |
valueOnly ? ( | |
error ? ( | |
<ErrorMessage error={error}/> | |
) : loaded ? ( | |
children(value) | |
) : ( | |
<Loading/> | |
) | |
) : ( | |
children(this.state) | |
) | |
) : null | |
} | |
} | |
export default Ref |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SAMPLE USAGE | |
const addAccountToTransactions = (txs, accounts) => ( | |
txs.map(tx => ( | |
// add the relationship | |
{ | |
...tx, | |
account: accounts[tx.accountKey] | |
} | |
)) | |
) | |
const TransactionsWithAccounts = () => ( | |
// these fetch in parallel | |
<Ref path="/accounts"> | |
{(accounts) => ( | |
<Ref path="/transactions" list={true}> | |
{(txs) => ( | |
// txs will be an array | |
accounts.error || txs.error ? ( | |
<ErrorMessages errors={[ accounts.error, txs.error ]}/> | |
) : accounts.loaded && txs.loaded ? ( | |
children(addAccountToTransactions(transactions.value, accounts.value)) | |
) : ( | |
<Loading/> | |
) | |
)} | |
</Ref> | |
)} | |
</Ref> | |
) | |
// now it's super easy to just grab that data from anywhere, even w/ the foreign relationship! | |
const TransactionsDashboard = () => ( | |
<Block> | |
<Header>Transactions</Header> | |
<TransactionsWithAccounts> | |
{(txs) => ( | |
<Table txs={formatTransactions(txs)}/> | |
)} | |
</TransactionsWithAccounts> | |
</Block> | |
) |
@ryanflorence awesome man, thanks for sharing. I think you can preserve the firebase order if you changed your listToArray
https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot#forEach
function listToArray(snapshot) {
const list = [];
// snapshot implements forEach to obey order
snapshot.forEach((v) => list.push({...v, key: v.key}) );
return list;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is pretty cool. I just tried this out. However, getting an invalid element error unless I
children(value)
with<span>
: