Skip to content

Commit acdf2e2

Browse files
author
sw-yx
committed
init
1 parent 2ae1fc1 commit acdf2e2

File tree

5 files changed

+324
-53
lines changed

5 files changed

+324
-53
lines changed

README.md

+175-10
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,196 @@
11
# react-netlify-identity
22

3-
> use netlify identity with react
3+
> Netlify Identity + React Hooks, with Typescript
44
55
[![NPM](https://img.shields.io/npm/v/react-netlify-identity.svg)](https://www.npmjs.com/package/react-netlify-identity) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
66

7+
Use [Netlify Identity](https://www.netlify.com/docs/identity/) easier with React! This is a thin wrapper over the [gotrue-js](https://github.com/netlify/gotrue-js) library for easily accessing Netlify Identity functionality in your app, with React Hooks. Types are provided.
8+
9+
You can find [a full demo here](https://netlify-gotrue-in-react.netlify.com/) with [source code](https://github.com/netlify/create-react-app-lambda/tree/reachRouterAndGoTrueDemo/src).
10+
11+
**This library is not officially maintained by Netlify.** This is written by swyx for his own use (and others with like minds 😎). See below for official alternatives.
12+
13+
## Official Alternatives
14+
15+
This is a "headless" library, meaning there is no UI exported and you will write your own UI to work with the authentication.
16+
17+
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
18+
19+
If you want a "widget" overlay that gives you a nice UI out of the box, at the cost of a larger bundle, check https://github.com/netlify/netlify-identity-widget
20+
21+
If you want a popup window approach also with a nice UI out of the box, and don't mind the popup flow, check https://github.com/netlify/netlify-auth-providers
22+
23+
## Typescript
24+
25+
Library is written in Typescript. File an issue if you find any problems.
26+
727
## Install
828

929
```bash
10-
npm install --save react-netlify-identity
30+
yarn add react-netlify-identity
1131
```
1232

1333
## Usage
1434

35+
**As a React Hook**, you can destructure these variables and methods:
36+
37+
- `user: User`
38+
- `setUser`: directly set the user object. Not advised; use carefully!! mostly you should use the methods below
39+
- `isConfirmedUser: boolean`: if they have confirmed their email
40+
- `isLoggedIn: boolean`: if the user is logged in
41+
- `signupUser(email: string, password: string, data: Object)`
42+
- `loginUser(email: string, password: string)`
43+
- `logoutUser()`
44+
- `requestPasswordRecovery(email: string)`
45+
- `recoverAccount(token: string, remember?: boolean | undefined)`
46+
- `updateUser(fields: Object)`
47+
- `getFreshJWT()`
48+
- `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)
49+
50+
<details>
51+
<summary>
52+
<b>
53+
Example code
54+
</b>
55+
</summary>
56+
1557
```tsx
16-
import * as React from 'react'
58+
import * as React from 'react';
1759

18-
import MyComponent from 'react-netlify-identity'
60+
import { useNetlifyIdentity } from './useNetlifyIdentity';
1961

20-
class Example extends React.Component {
21-
render () {
22-
return (
23-
<MyComponent />
24-
)
25-
}
62+
const IdentityContext = React.createContext(); // not necessary but recommended
63+
function App() {
64+
const identity = useNetlifyIdentity(url);
65+
return (
66+
<IdentityContext.Provider value={identity}>
67+
{/* rest of your app */}
68+
</IdentityContext.Provider>
69+
);
2670
}
71+
72+
// log in/sign up example
73+
function Login() {
74+
const { loginUser, signupUser } = React.useContext(IdentityContext);
75+
const formRef = React.useRef();
76+
const [msg, setMsg] = React.useState('');
77+
const [isLoading, load] = useLoading();
78+
const signup = () => {
79+
const email = formRef.current.email.value;
80+
const password = formRef.current.password.value;
81+
load(signupUser(email, password))
82+
.then(user => {
83+
console.log('Success! Signed up', user);
84+
navigate('/dashboard');
85+
})
86+
.catch(err => console.error(err) || setMsg('Error: ' + err.message));
87+
};
88+
return (
89+
<form
90+
ref={formRef}
91+
onSubmit={e => {
92+
e.preventDefault();
93+
const email = e.target.email.value;
94+
const password = e.target.password.value;
95+
load(loginUser(email, password))
96+
.then(user => {
97+
console.log('Success! Logged in', user);
98+
navigate('/dashboard');
99+
})
100+
.catch(err => console.error(err) || setMsg('Error: ' + err.message));
101+
}}
102+
>
103+
<div>
104+
<label>
105+
Email:
106+
<input type="email" name="email" />
107+
</label>
108+
</div>
109+
<div>
110+
<label>
111+
Password:
112+
<input type="password" name="password" />
113+
</label>
114+
</div>
115+
{isLoading ? (
116+
<Spinner />
117+
) : (
118+
<div>
119+
<input type="submit" value="Log in" />
120+
<button onClick={signup}>Sign Up </button>
121+
{msg && <pre>{msg}</pre>}
122+
</div>
123+
)}
124+
</form>
125+
);
126+
}
127+
128+
// log out user
129+
function Logout() {
130+
const { logoutUser } = React.useContext(IdentityContext);
131+
return <button onClick={logoutUser}>You are signed in. Log Out</button>;
132+
}
133+
134+
// check `identity.user` in a protected route
135+
function PrivateRoute(props) {
136+
const identity = React.useContext(IdentityContext);
137+
let { as: Comp, ...rest } = props;
138+
return identity.user ? (
139+
<Comp {...rest} />
140+
) : (
141+
<div>
142+
<h3>You are trying to view a protected page. Please log in</h3>
143+
<Login />
144+
</div>
145+
);
146+
}
147+
148+
// check if user has confirmed their email
149+
// use authedFetch API to make a request to Netlify Function with the user's JWT token,
150+
// letting your function use the `user` object
151+
function Dashboard() {
152+
const props = React.useContext(IdentityContext);
153+
const { isConfirmedUser, authedFetch } = props;
154+
const [isLoading, load] = useLoading();
155+
const [msg, setMsg] = React.useState('Click to load something');
156+
const handler = () => {
157+
load(authedFetch.get('/.netlify/functions/authEndPoint')).then(setMsg);
158+
};
159+
return (
160+
<div>
161+
<h3>This is a Protected Dashboard!</h3>
162+
{!isConfirmedUser && (
163+
<pre style={{ backgroundColor: 'papayawhip' }}>
164+
You have not confirmed your email. Please confirm it before you ping
165+
the API.
166+
</pre>
167+
)}
168+
<hr />
169+
<div>
170+
<p>You can try pinging our authenticated API here.</p>
171+
<p>
172+
If you are logged in, you should be able to see a `user` info here.
173+
</p>
174+
<button onClick={handler}>Ping authenticated API</button>
175+
{isLoading ? <Spinner /> : <pre>{JSON.stringify(msg, null, 2)}</pre>}
176+
</div>
177+
</div>
178+
);
179+
}
180+
```
181+
182+
This is also exported as a render prop component, `NetlifyIdentity`, but we're not quite sure if its that useful if you can already use hooks:
183+
184+
```tsx
185+
<NetlifyIdentity domain="https://mydomain.netlify.com">
186+
{({ loginUser, signupUser }) => {
187+
// use it
188+
}}
189+
</NetlifyIdentity>
27190
```
28191

192+
</details>
193+
29194
## License
30195

31196
MIT © [sw-yx](https://github.com/sw-yx)

package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-netlify-identity",
3-
"version": "1.0.0",
3+
"version": "0.0.1",
44
"description": "use netlify identity with react",
55
"author": "sw-yx",
66
"license": "MIT",
@@ -30,14 +30,15 @@
3030
"devDependencies": {
3131
"@svgr/rollup": "^2.4.1",
3232
"@types/jest": "^23.1.5",
33-
"@types/react": "^16.3.13",
34-
"@types/react-dom": "^16.0.5",
33+
"@types/react": "^16.7.13",
34+
"@types/react-dom": "^16.0.11",
3535
"babel-core": "^6.26.3",
3636
"babel-runtime": "^6.26.0",
3737
"cross-env": "^5.1.4",
3838
"gh-pages": "^1.2.0",
39-
"react": "^16.4.1",
40-
"react-dom": "^16.4.1",
39+
"gotrue-js": "^0.9.24",
40+
"react": "^16.7.0-alpha.2",
41+
"react-dom": "^16.7.0-alpha.2",
4142
"react-scripts-ts": "^2.16.0",
4243
"rollup": "^0.62.0",
4344
"rollup-plugin-babel": "^3.0.7",

src/index.tsx

+115-14
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,123 @@
22
* @class ExampleComponent
33
*/
44

5-
import * as React from 'react'
5+
import * as React from 'react';
66

7-
import styles from './styles.css'
7+
import GoTrue, { User } from 'gotrue-js';
88

9-
export type Props = { text: string }
9+
type authChangeParam = (user?: User) => string | void;
1010

11-
export default class ExampleComponent extends React.Component<Props> {
12-
render() {
13-
const {
14-
text
15-
} = this.props
11+
interface NIProps {
12+
children: any;
13+
domain: string;
14+
onAuthChange?: authChangeParam;
15+
}
16+
const NetlifyIdentity = ({ children, domain, onAuthChange }: NIProps) =>
17+
children(useNetlifyIdentity(domain, onAuthChange));
18+
19+
export default NetlifyIdentity;
20+
export function useNetlifyIdentity(
21+
domain: string,
22+
onAuthChange: authChangeParam = () => {}
23+
) {
24+
const authRef = React.useRef(
25+
new GoTrue({
26+
APIUrl: `${domain}/.netlify/identity`,
27+
setCookie: true
28+
})
29+
);
30+
31+
const [user, setUser] = React.useState<User | undefined>(undefined);
32+
const _setUser = (_user: User | undefined) => {
33+
setUser(_user);
34+
onAuthChange(_user); // if someone's subscribed to auth changes, let 'em know
35+
return _user; // so that we can continue chaining
36+
};
37+
/******* OPERATIONS */
38+
// make sure the Registration preferences under Identity settings in your Netlify dashboard are set to Open.
39+
const signupUser = (email: string, password: string, data: Object) =>
40+
authRef.current.signup(email, password, data).then(_setUser); // TODO: make setUser optional?
41+
const loginUser = (email: string, password: string) =>
42+
authRef.current.login(email, password).then(_setUser);
43+
const requestPasswordRecovery = (email: string) =>
44+
authRef.current.requestPasswordRecovery(email);
45+
const recoverAccount = (token: string, remember?: boolean | undefined) =>
46+
authRef.current.recover(token, remember);
47+
const updateUser = (fields: Object) => {
48+
if (user == null) {
49+
throw new Error('No current user found - are you logged in?');
50+
} else {
51+
return user!
52+
.update(fields) // e.g. { email: "example@example.com", password: "password" }
53+
.then(_setUser);
54+
}
55+
};
56+
const getFreshJWT = () => {
57+
if (!user) throw new Error('No current user found - are you logged in?');
58+
return user.jwt();
59+
};
60+
const logoutUser = () => {
61+
if (!user) throw new Error('No current user found - are you logged in?');
62+
return user.logout().then(() => _setUser(undefined));
63+
};
64+
65+
const genericAuthedFetch = (method: string) => (
66+
endpoint: string,
67+
obj = {}
68+
) => {
69+
if (!user || !user.token || !user.token.access_token)
70+
throw new Error('no user token found');
71+
const defaultObj = {
72+
headers: {
73+
Accept: 'application/json',
74+
'Content-Type': 'application/json',
75+
Authorization: 'Bearer ' + user.token.access_token
76+
}
77+
};
78+
const finalObj = Object.assign(defaultObj, { method }, obj);
79+
return fetch(endpoint, finalObj).then(res =>
80+
finalObj.headers['Content-Type'] === 'application/json' ? res.json() : res
81+
);
82+
};
83+
const authedFetch = {
84+
get: genericAuthedFetch('GET'),
85+
post: genericAuthedFetch('POST'),
86+
put: genericAuthedFetch('PUT'),
87+
delete: genericAuthedFetch('DELETE')
88+
};
89+
90+
// // confirmation
91+
// http://lea.verou.me/2011/05/get-your-hash-the-bulletproof-way/
92+
React.useEffect(() => {
93+
const hash = window.location.hash.substring(1);
94+
if (hash.slice(0, 19) === 'confirmation_token=') {
95+
// we are in a confirmation!
96+
const token = hash.slice(19);
97+
authRef.current
98+
.confirm(token)
99+
.then(_setUser)
100+
.catch(console.error);
101+
// .then(
102+
// () =>
103+
// (window.location =
104+
// window.location.origin + window.location.pathname) // strip hash
105+
// )
106+
}
107+
}, []);
16108

17-
return (
18-
<div className={styles.test}>
19-
Example Component: {text}
20-
</div>
21-
)
22-
}
109+
/******* hook API */
110+
return {
111+
user,
112+
setUser, // use carefully!! mostly you should use the methods below
113+
isConfirmedUser: !!(user && user.confirmed_at),
114+
isLoggedIn: !!user,
115+
signupUser,
116+
loginUser,
117+
logoutUser,
118+
requestPasswordRecovery,
119+
recoverAccount,
120+
updateUser,
121+
getFreshJWT,
122+
authedFetch
123+
};
23124
}

src/styles.css

-8
This file was deleted.

0 commit comments

Comments
 (0)