Skip to content

Commit

Permalink
most of the hooks work now
Browse files Browse the repository at this point in the history
need to add __user parsing
  • Loading branch information
aleksanderbrymora committed Aug 29, 2020
1 parent 7cd5721 commit 7174ba3
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 118 deletions.
277 changes: 167 additions & 110 deletions docs/firestore.md
Original file line number Diff line number Diff line change
@@ -1,134 +1,191 @@
# Write

Writing to the Firestore is simple too! All you need to do is import a hook that you want to use and initialize it with a string pointing to the collection you'll do the operation on.
You'll always get an array of:
- `loading` state, that indicates if the process is underway. Useful for disabling a button after submission or showing a spinner.
- `error` which by default is `null`, but when there is a problem with be populated with an error message created by firebase
- `function` that does the action that you requested, by calling chosen hook

All of the hooks take `collection` string as a parameter. Have a look at the examples below for more in detail information about that, and occasional extra parameters.

All of the mutations get timestamps attached to them by default so you don't have to worry about them

## Set
Also all of the mutation hooks support passing in a string of `__user` to indicate that you want to replace it with currently logged in user id. [Read more about how that works here](./other.md)

## `useSet`

Use when you have an id of thing you want to change or if you want to create an item with given id.

`useSet` takes two arguments. Besides, as always, a `collection` string, it also takes an optional `merge` boolean (default `false`) that specifies if you want to keep previous values or overwrite the whole document with passed in object.

The `setFunction` it returns takes two arguments:
- `uid` of the document you want to set
- `data` object you want to set it to

### Example

```jsx
// Initialize the hook with parameters
// first is operation, which is a type of change you want to do on the Firestore
// second is a name of the collection in Firestore you want to change
// then there is an object for all of the options - always consult docs if you want to use them
// as they change for different operations, though if you don't pass them they all have defaults

// pro tip - its helpful to read the hook like this:
// const setCity = useWriteFS('set', {/* document in */} 'city', {

const setCity = useWriteFS('set', 'city', {
merge: true, // default false
});

// Example in a submit action:
// Function needs to be async as the setCity returns a promise
handleSubmit = async () => {
// we need a try catch block in this async function as writing to db is done over the network,
// and stuff can always go wrong
try {
// need to await the operation to resolve successfully
// function doesn't return anything so no need to capture the outcome
await setCity({data: 'that', you: 'want', to: 'save'}, 'uidOfTheDocYouWantToChangeOrCreate')
// after its done you can call any other function like redirect to other path:
props.history.push('/')
} catch (e) {
// handle the error. Might have been caused by collection not existing for example
}
}
import React, { useState } from 'react';
import { useSet } from 'firebase-hooks-react';

const App = () => {
// we need to point the hook to the collection with the parameter string
const [loading, error, setName] = useSet('names', {/* optional merge boolean */} true);
const [name, setNameField] = useState('')

const onSubmit = (e) => {
e.preventDefault();
// then we can use it wherever we want, like in this submit function
// that sends an object to the database with key of name and value of name (thats in the state)
setName('testing', { name });
};

return (
<div className='App'>
<form onSubmit={onSubmit}>
<input type='text' value={name} onChange={e => setNameField(e.target.value)} />
{/* useful thing is using loading boolean for disabling the button */}
<input type='submit' value='Change your name' disabled={loading} />
{/* you should probably do some more error handling than this, but its quick and dirty and does the work here */}
{error && JSON.stringify(error)}
</form>
</div>
);
};

export default App;
```

## Add
## `useAdd`

Use when you don't want to think about what id you want to give something, you just want to add a new doc to the collection. Only takes a `collection` string.

The `addFunction` that the hook returns takes one argument:
- `data` object containing the data you want to add.

### Example

```jsx
// same process as with set, so for details have a look above
// same pro-tip as above - its useful to read it like this:
// const setCity = useWriteFS('add', {/* document to */} 'city')

// This time there are no options as we let firestore figure out the id
const addCity = useWriteFS('add', 'city');

// Example in a submit action:
// Function needs to be async as the addCity returns a promise
handleSubmit = async () => {
try {
// need to await the operation to resolve successfully
// function doesn't return anything so no need to capture the outcome
await addCity({data: 'that', you: 'want', to: 'save'})
// after its done you can call any other function like redirect to other path:
props.history.push('/')
} catch (e) {
// handle the error. Might have been caused by collection not existing for example
}
}
import React, { useState } from 'react';
import { useAdd } from 'firebase-hooks-react';

const App = () => {
// we need to point the hook to the collection with the parameter string
const [loading, error, addName] = useAdd('names');
const [name, setName] = useState('')

const onSubmit = (e) => {
e.preventDefault();
// then we can use it wherever we want
addName({ name });
};

return (
<div className='App'>
<form onSubmit={onSubmit}>
<input type='text' value={name} onChange={e => setName(e.target.value)} />
{/* useful thing is using loading boolean for disabling the button */}
<input type='submit' value='Add new name' disabled={loading} />
{error && JSON.stringify(error)}
</form>
</div>
);
};

export default App;
```

## Update
## `useUpdate`

Useful if you have a document that you know the id of and you want to update one field in it. Pretty much the same as using the `useSet` hook with merge set to true. Takes only a `collection` string.

The `updateFunction`, that the hook returns, takes two arguments:
- `uid` of the document you want to update
- `data` object with fields that you want to update in a document

```jsx
// Very similar structure to Set function, the difference is only in the string that you have to pass,
// and that there are no extra options to pass
// a reading pro-tip again:
// const setCity = useWriteFS('update', {/* document in */} 'city')
const updateCity = useWriteFS('update', 'city');

// Example in a submit action:
// Function needs to be async as the updateCity returns a promise
handleSubmit = async () => {
try {
// we need to know what document you want to update - by passing its id as first arg
// second parameter is an object of existing values you want to update
// consult with these docs on what you can actually do with that as there are cool things like increase by one
// https://firebase.google.com/docs/firestore/manage-data/add-data#update-data
await updateCity({data: 'that', you: 'want', to: 'save'}, 'uidOfTheDocYouWantToChangeOrCreate')
// after its done you can call any other function like redirect to other path:
props.history.push('/')
} catch (e) {
// handle the error. Might have been caused by collection not existing for example
}
}
import React, { useState } from 'react';
import { useUpdate } from 'firebase-hooks-react';

const App = () => {
// we need to point the hook to the collection with the parameter string
const [loading, error, updateName] = useUpdate('names');
const [name, setName] = useState('')

const onSubmit = (e) => {
e.preventDefault();
// then we can use it wherever we want
updateName('testing', { name });
};

return (
<div className='App'>
<form onSubmit={onSubmit}>
<input type='text' value={name} onChange={e => setName(e.target.value)} />
{/* useful thing is using loading boolean for disabling the button */}
<input type='submit' value='Change your name' disabled={loading} />
{error && JSON.stringify(error)}
</form>
</div>
);
};

export default App;
```

## Delete
## `useDelete`

Simply deletes a document in the firebase, based on the id that you give it. Takes a `collection` string as always.

The `deleteFunction`, that the hook returns, takes one argument:
- `uid` of the document you want to delete

```jsx
// You might get the theme that these hooks are meant to look very similar and no changes here
// one thing you have to keep in mind that deleting a document doesnt delete its children,
// you can read about it here: https://firebase.google.com/docs/firestore/manage-data/delete-data#collections
const deleteCity = useWriteFS('delete', {/*document in*/} 'city');

// Example in a submit action:
// Function needs to be async as the updateCity returns a promise
handleSubmit = async () => {
try {
// we need to know what document you want to delete - we do that by passing its id as first arg
// It doesnt return anything
await deleteCity('uidOfTheDocYouWantToChangeOrCreate')
// after its done you can call any other function like redirect to other path:
props.history.push('/')
} catch (e) {
// handle the error. Might have been caused by collection not existing for example
}
}
import React from 'react';
import { useDelete } from 'firebase-hooks-react';

const App = () => {
// we need to point the hook to the collection with the parameter string
const [loading, error, deleteName] = useDelete('names');

const handleClick = () => {
// then we can use it wherever we want
deleteName('testing');
};

return (
<button value='Delete some name' onClick={handleClick} />
);
};

export default App;
```

## Delete a field
## `useDeleteFields`

Only deletes specified fields from a chosen document

The `deleteFieldsFunction`, that the hook returns, takes two arguments:
- `uid` of the document you want to update
- `fields` which is an array of strings of fields that you want to delete in a document

```jsx
// probably the least useful but still here
// similar to delete action, just accepts an extra argument which is an array of fields to delete
const deleteFieldInCity = useWriteFS('deleteField', {/*in a document in the*/} 'city');

// Example in a submit action:
// Function needs to be async as the deleteFieldInCity returns a promise
handleSubmit = async () => {
try {
// we need to know in what document you want to delete passed fields - we do that by passing its id as first arg
// then we need to know what fields you want to delete, pass them as an array of strings
// It doesn't return anything
await setCity('uidOfTheDocYouWantToChangeOrCreate', ['capital', 'population'])
// after its done you can call any other function like redirect to other path:
props.history.push('/')
} catch (e) {
// handle the error. Might have been caused by collection not existing for example
}
}
import React from 'react';
import { useDeleteFields } from 'firebase-hooks-react';

const App = () => {
// we need to point the hook to the collection with the parameter string
const [loading, error, deleteFieldsInName] = useDelete('names');

const handleClick = () => {
// then we can use it wherever we want
deleteFieldsInName('testing', ['name']);
};

return (
<button value='Delete some fields in name' onClick={handleClick} />
);
};

export default App;
```
Empty file added docs/other.md
Empty file.
28 changes: 28 additions & 0 deletions docs/storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Storage

## brain-dump

- Add button that takes care of the whole image upload
- div that supports drag and drop
- note that someone should make them accessible like this: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Using_file_inputs

## flow

```js
return [loading, error, {
spreadToInput, // object to spread on the input, has type, onChange
spreadToSubmit, // object to spread on submit button to submit added file, disabled until file is added
uploadFunction, // simple function thats a part of the spreadToSubmit that purely uploads the files, useful when not wanting to add a submit button
progress, // an object with status of whats happening, read here
}] = useStorage(
'path',
{ // all of these are optional
accept: ['.jpg', 'image/*'], // and all the other types - more info [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers)
capture: 'accept' | 'user' | 'environment',
multiple: true | false,
metadata: {
// all of the metadata that the user might want to attach to the file, here are docs in FB https://firebase.google.com/docs/storage/web/upload-files#add_file_metadata
}
}
)
```
5 changes: 2 additions & 3 deletions src/firestore/read/useReadFS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useQuery } from './useQuery';
import { useReadCollection } from './useReadCollection';
import { useReadDoc } from './useReadDoc';

export const useReadFS = <QueryType extends QueryTypes>(
export const useRead = <QueryType extends QueryTypes>(
query: QueryType,
doc?: InferDocType<QueryType>,
// Todo this needs to be made type-safe with `inferReturnType`
Expand All @@ -23,8 +23,7 @@ export const useReadFS = <QueryType extends QueryTypes>(
handleError(firestore); // handle if not imported

// Checking if query has user in it and replacing it if needed
const { user } = useR();
const withUserQuery = handleUser(query, user) as QueryType; // not sure if that actually fixed it
const withUserQuery = handleUser(query) as QueryType; // not sure if that actually fixed it

// There are 3 parts to this hook:
// - collection string - useReadCollection => array of document objects
Expand Down
7 changes: 6 additions & 1 deletion src/firestore/write/useAdd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState } from 'react';
import { useFire } from '../../context';
import { timestamp } from '../../utils/addTimestamp';
import { isEmpty } from '../../utils/isEmpty';
import { convertToWithUser } from '../../utils/convertToWithUser';

type UseAddType = [boolean, null | Error, AddFunction]
type AddFunction = (data: object) => void
Expand All @@ -17,6 +18,9 @@ export const useAdd = (collection: string): UseAddType => {
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<null | Error>(null);

// converting collection to contain user id if `__user` was passed in
const withUserCollection = convertToWithUser(collection);

/**
* Function used to set a document in the firestore
* @param data - an object that will be set in the firestore
Expand All @@ -27,6 +31,7 @@ export const useAdd = (collection: string): UseAddType => {
*/
const addFunction = (data: object) => {
setLoading(true);
setError(null);
if (isEmpty(data)) {
setError(new Error('You need to specify the data you want to add'));
setLoading(false);
Expand All @@ -42,7 +47,7 @@ export const useAdd = (collection: string): UseAddType => {
};

(async () => {
const ref = firestore!.collection(collection);
const ref = firestore!.collection(withUserCollection);
try {
await ref.add(timestampedData);
setLoading(false);
Expand Down
1 change: 1 addition & 0 deletions src/firestore/write/useDelete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const useDelete = (collection: string): UseDeleteType => {
*/
const deleteFunction = (doc: string): void => {
setLoading(true);
setError(null);
if (!doc) {
setError(new Error('You need to pass an uid of the document you want to delete'));
setLoading(false);
Expand Down
Loading

0 comments on commit 7174ba3

Please sign in to comment.