Skip to content

Merge pull request #31 from sw-yx/v0.2.1 #31

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

Merged
merged 7 commits into from
Jan 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 87 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@ Three demos:
- [example with Reach Router](https://github.com/sw-yx/react-netlify-identity/tree/master/examples/example-reach-router) with [a demo hosted here](https://react-netlify-identity.netlify.com) and [deployed logs here](https://app.netlify.com/sites/react-netlify-identity/deploys)
- [example with React Router](https://github.com/sw-yx/react-netlify-identity/tree/master/examples/example-react-router) with [a demo hosted here](https://react-netlify-identity-example.netlify.com)


**This library is not officially maintained by Netlify.** This is written by swyx for his own use (and others with like minds 😎) and will be maintained as a personal project unless formally adopted by Netlify. See below for official alternatives.

## Blogposts

- [Add Netlify Identity Authentication to any React App in 5 minutes with React Context, Hooks and Suspense](https://dev.to/swyx/add-netlify-identity-authentication-to-any-react-app-in-5-minutes-with-react-context-hooks-and-suspense-5gci)


## List of Alternatives

**Lowest level JS Library**: If you want to use the official Javascript bindings to GoTrue, Netlify's underlying Identity service written in Go, use https://github.com/netlify/gotrue-js
Expand Down Expand Up @@ -51,13 +49,19 @@ yarn add react-netlify-identity
- `isConfirmedUser: boolean`: if they have confirmed their email
- `isLoggedIn: boolean`: if the user is logged in
- `signupUser(email: string, password: string, data: Object)`
- `loginUser(email: string, password: string, remember: Boolean)` - we default the `remember` term to `true` since you'll usually want to remember the session in localStorage. set it to false if you need to
- `loginUser(email: string, password: string, remember: boolean = true)` - we default the `remember` term to `true` since you'll usually want to remember the session in localStorage. set it to false if you need to
- `logoutUser()`
- `requestPasswordRecovery(email: string)`
- `recoverAccount(token: string, remember?: boolean | undefined)`
- `updateUser(fields: { data: object })`
- `getFreshJWT()`
- `authedFetch(endpoint: string, obj = {})` (a thin axios-like wrapper over `fetch` that has the user's JWT attached, for convenience pinging Netlify Functions with Netlify Identity)
- `authedFetch(endpoint: string, obj: RequestInit = {})` a thin axios-like wrapper over `fetch` that has the user's JWT attached, for convenience pinging Netlify Functions with Netlify Identity
- `param: TokenParam`
- a token exposing Netlify tokens a dev has to implement the actions for; namely `invite`, `recovery`, `email_change` and `access_denied`
- **Important:** tokens this package exposes no methods for are automatically handled and will not be passed down - see [runRoutes implementation](https://github.com/sw-yx/react-netlify-identity/master/src/runRoutes.tsx)
- if you don't want this behaviour (added [here](https://github.com/sw-yx/react-netlify-identity/issues/12) in v.0.1.8), pass `runRoutes={false}` to the exposed hook
- for further reference, please check the [type definition](https://github.com/sw-yx/react-netlify-identity/tree/master/src/token.ts)
- an example implementation for a Recovery process can be found below
- `recoverAccount(remember?: boolean)`: verifies and consumes the recovery token caught by `runRoutes`, sets user on success

```tsx
import React from 'react';
Expand Down Expand Up @@ -89,16 +93,19 @@ function Login() {
const { loginUser, signupUser } = useIdentityContext();
const formRef = React.useRef();
const [msg, setMsg] = React.useState('');

const signup = () => {
const email = formRef.current.email.value;
const password = formRef.current.password.value;

signupUser(email, password)
.then(user => {
console.log('Success! Signed up', user);
navigate('/dashboard');
})
.catch(err => console.error(err) || setMsg('Error: ' + err.message));
};

return (
<form
ref={formRef}
Expand Down Expand Up @@ -189,6 +196,81 @@ function Dashboard() {

</details>

<details>
<summary>
<h3>
How to handle a Recovery Action (since v0.2)
</h3>
</summary>

Of course you can alternatively inline this logic into app.

```tsx
import { useIdentityContext } from 'react-netlify-identity';
import {
BrowserRouter as Router,
Switch,
Route,
useLocation,
useHistory,
} from 'react-router-dom';

export default function App() {
const { isLoggedIn } = useIdentityContext();

return (
<Router>
<CatchNetlifyRecoveryNullComponent />
<Switch>
{isLoggedIn ? (
<>
<Route path="/dashboard" exact component={DashboardPage} />
<Route component={() => <Redirect to="/dashbard" />} />
</>
) : (
<>
<Route path="/" exact component={LandingPage} />
<Route path="/register" exact component={RegisterPage} />
<Route path="/login" exact component={LoginPage} />
{/* etc */}
<Route path="/recovery" exact component={RecoveryPage} />
<Route component={() => <Redirect to="/" />} />
</>
)}
</Switch>
</Router>
);
}

function CatchNetlifyRecoveryNullComponent() {
const {
param: { token, type },
} = useIdentityContext();
const { replace } = useHistory();
const { pathname } = useLocation();

// important to check for the current pathname here because else you land
// in a infinite loop
if (token && type === 'recovery' && pathname === '/') {
replace(`/recovery`, { token });
}

return null;
}

function RecoveryPage() {
const {
location: { state },
} = useHistory();
// this state _might_ not be needed, it was needed in my specific implementation
const [token] = useState(state?.token);

return null; // set new password in a form and call updateUser
}
```

</details>

## Lower level API: `useNetlifyIdentity`

If you'd like to handle your own context yourself, you can use this library as a hook as well:
Expand All @@ -201,8 +283,6 @@ function useNetlifyIdentity(
): ReactNetlifyIdentityAPI;
```

the library watches for and handles confirmation routes by default. If you don't like this, pass `enableRunRoutes: false`. This was added here https://github.com/sw-yx/react-netlify-identity/issues/12 in v0.1.8

## License

MIT © [sw-yx](https://github.com/sw-yx)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-netlify-identity",
"version": "0.2.0",
"version": "0.2.1",
"description": "use netlify identity with react",
"author": "sw-yx",
"license": "MIT",
Expand Down
24 changes: 13 additions & 11 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import React, {
useEffect,
createContext,
useContext,
useCallback,
// types
Dispatch,
SetStateAction,
ReactNode,
useCallback,
} from 'react';

import GoTrue, {
Expand Down Expand Up @@ -48,7 +46,7 @@ type MaybeUserPromise = Promise<User | undefined>;
export type ReactNetlifyIdentityAPI = {
user: User | undefined;
/** not meant for normal use! you should mostly use one of the other exported methods to update the user instance */
setUser: Dispatch<SetStateAction<User | undefined>>;
setUser: (_user: GoTrueUser | undefined) => GoTrueUser | undefined;
isConfirmedUser: boolean;
isLoggedIn: boolean;
signupUser: (
Expand Down Expand Up @@ -218,16 +216,20 @@ export function useNetlifyIdentity(
);

const recoverAccount = useCallback(
(remember?: boolean | undefined) => {
(remember?: boolean) => {
if (!param.token || param.type !== 'recovery') {
throw new Error(errors.tokenMissingOrInvalid);
}

return goTrueInstance.recover(param.token, remember).then(user => {
// clean up consumed token
setParam(defaultParam);
return _setUser(user);
});
return goTrueInstance
.recover(param.token, remember)
.then(user => {
return _setUser(user);
})
.finally(() => {
// clean up consumed token
setParam(defaultParam);
});
},
[goTrueInstance, _setUser, param]
);
Expand Down Expand Up @@ -294,7 +296,7 @@ export function useNetlifyIdentity(
return {
user,
/** not meant for normal use! you should mostly use one of the other exported methods to update the user instance */
setUser,
setUser: _setUser,
isConfirmedUser: !!(user && user.confirmed_at),
isLoggedIn: !!user,
signupUser,
Expand Down
4 changes: 2 additions & 2 deletions src/token.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export type TokenParam = {
token: string | undefined;
type: 'invite' | 'recovery' | 'email_change' | undefined;
error?: 'access_denied';
status?: 403;
error: 'access_denied' | undefined;
status: 403 | undefined;
};

export const defaultParam: TokenParam = {
Expand Down