Vamos aprender a configurar o Root Import para que possamos substituir essa sintaxe de import:
import something from '../../../src/something';
Para essa:
import something from '~/src/something';
Instale as seguintes dependencias
yarn add customize-cra react-app-rewired babel-plugin-root-import eslint-import-resolver-babel-plugin-root-import -D
Na raiz do projeto crie um arquivo com o nome config-overrides.js
.
touch config-overrides.js
Edite esse arquivo e deixe-o dessa maneira:
const { addBabelPlugin, override } = require('customize-cra');
module.exports = override(
addBabelPlugin([
'babel-plugin-root-import',
{
rootPathSuffix: 'src',
},
])
);
Agora vamos mudar algumas configurações do arquivo package.json
.
Deixe isso:
{
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
}
Dssa forma:
{
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
}
Agora vamos configurar o eslint para que ele pare de reclamar com nossa nova maneira de importar os aqruivos.
Abra seu arquivo .eslintrc.js
, após rules
, adicione a seguinte configuração:
{
settings: {
'import/resolver': {
'babel-plugin-root-import': {
rootPathSuffix: 'src',
},
},
},
}
Agora vamos configurar para que o VSCode não se perca nas importações dessa nova maneira.
Crie um arquivo na raiz do projeto com o nome jsconfig.json
.
touch jsconfig.json
edite-o e deixe da seguinte forma:
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"~/*": ["*"]
}
}
}
E fim, tudo volta a funcionar perfeitamente :)
Agora vamos validar os dados do formulário de Login, na página SignIn
, edite-o, e deixe-o dessa forma:
import React from 'react';
import { Form, Input } from '@rocketseat/unform';
import * as Yup from 'yup'; // importei o Yup
import logo from '~/assets/images/logo.png';
const schema = Yup.object().shape({ // Criei um schema, e algumas validações.
email: Yup.string()
.email('Digite um email válido')
.required(),
password: Yup.string().required('A senha é obrigatória'),
});
export default function SignIn() {
function handleSubmit({ email, password }) {
console.tron.log(email, password);
}
return (
<>
<img src={logo} alt="gympoint" />
<Form schema={schema} {/*Aqui eu passei como parâmeytro do unform, pode ser estilizado como um span*/} onSubmit={handleSubmit} action="">
<Input name="email" type="email" placeholder="seu e-mail" />
<Input
name="password"
type="password"
placeholder="Sua Senha secreta"
/>
<button type="submit">Acessar</button>
</Form>
</>
);
}
// src/pages/SignIn/index.js
Aqui vamos configurar o Store, onde vamos fazer a autenticação da plataforma, aqui vamos utilizar o Redux, Redux-Saga e Etc. Come adicionando essas bibliotecas:
yarn add redux redux-saga react-redux reactotron-redux reactotron-redux-saga immer
Dentro de src
, crie as seguintes pastas e arquivos.
mkdir src/store src/store/modules src/store/modules/auth && touch src/store/index.js touch src/store/createStore.js src/store/modules/rootReducer.js src/store/modules/rootSaga.js src/store/modules/auth/actions.js src/store/modules/auth/reducer.js src/store/modules/auth/sagas.js
O esquema de pastas e arquivos deve ficar dessa forma:
store
├── modules
│ ├── auth
│ │ ├── actions.js
│ │ ├── reducer.js
│ │ └── sagas.js
│ ├── rootReducer.js
│ └── rootSaga.js
├── createStore.js
└── index.js
Vamos editar o aquivo src/store/modules/auth/reducer.js
, deixe-o dessa forma:
const INITIAL_STATE = {}; // squi é o estado inicial da aplicação
export default function auth(state = INITIAL_STATE, action) {
switch (action.type) {
default:
return state;
}
}
// src/store/modules/auth/reducer.js
Agora vamos editar o aquivo src/store/modules/auth/sagas.js
:
import { all } from 'redux-saga/effects';
export default all([]);
// src/store/modules/auth/sagas.js
Agora vamos editar o src/store/modules/rootReducer.js
:
import { combineReducers } from 'redux';
import auth from './auth/reducer';
export default combineReducers({
auth,
});
// src/store/modules/rootReducer.js
Agora vamos editar o aquivo src/store/modules/rootSaga.js
:
import { all } from 'redux-saga/effects';
import auth from './auth/sagas';
export default function* rootSaga() {
return yield all([auth]);
}
//src/store/modules/rootSaga.js
Agora vamos configurar o arquivo src/store/index.js
:
import createSagaMiddleware from 'redux-saga';
import createStore from './createStore';
import rootReducer from './modules/rootReducer';
import sagas from './modules/rootSaga';
const middlewares = [];
const sagaMonitor =
process.env.NODE_ENV === 'development'
? console.tron.createSagaMonitor()
: null;
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
middlewares.push(sagaMiddleware);
const store = createStore(rootReducer, middlewares);
sagaMiddleware.run(sagas);
export default store;
// src/store/index.js
Agora vamo editar o arquivo src/store/createStore.js
:
import { createStore, compose, applyMiddleware } from 'redux';
export default (reducers, middlewares) => {
const enhancer =
process.env.NODE_ENV === 'development'
? compose(console.tron.createEnhancer(), applyMiddleware(...middlewares))
: applyMiddleware(...middlewares);
return createStore(reducers, enhancer);
};
// src/store/createStore.js
Agora vamos nas configurações do Reactotron, edite o arquivo src/config/ReactotronConfig.js
:
import Reactotron from 'reactotron-react-js';
import { reactotronRedux } from 'reactotron-redux';
import reactotronSaga from 'reactotron-redux-saga';
if (process.env.NODE_ENV === 'development') {
const tron = Reactotron.configure()
.use(reactotronRedux())
.use(reactotronSaga())
.connect();
tron.clear();
console.tron = tron;
}
Agora vamos no arquivo src/App.js
, vamos editar ele:
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import './config/ReactotronConfig';
import store from './store'; // deve vir depois da configuração do reactotron
import Routes from './routes';
import history from './services/history';
import GlobalStyle from './styles/global';
function App() {
return (
<Provider store={store}>
<Router history={history}>
<Routes />
<GlobalStyle />
</Router>
</Provider>
);
}
export default App;
// src/App.js
Agora com nosso store configurado, vamos começar a criar a autenticação.
Vamos Editar o aquivo src/store/modules/auth/actions.js
:
export function signInRequest(email, password) {
return {
type: '@auth/SIGN_IN_REQUEST',
payload: { email, password },
};
}
export function signInSuccess(token, user) {
return {
type: '@auth/SIGN_IN_SUCCESS',
payload: { token, user },
};
}
export function signFailure() {
return {
type: '@auth/SIGN_FAILURE',
};
}
// src/store/modules/auth/actions.js
Aqui criamos 3 actions para a autenticação, uma para fazer request a API
, outra pra pra successo, e uma pra qualquer falha.
Agora vamos instalar uma biblioteca pra lidar com requisições a
API
.
yarn add axios
Agora vamos configurar nosso arquivo de service-api
, crie um arquivo com o nome api.js
dentro da pasta src/services
.
touch src/services/api.js
Edite o arquivo que acabamos de criar com as configurações padoes do AXIOS
:
import axios from 'axios';
const api = axios.create({
baseURL: 'https://localhost:444',
});
// src/services/api.js
Configure a URL
base da sua API
com sua respectiva porta.
Agora vamos Editar nosso saga
de autenticação, edite o arquivo src/store/modules/auth/sagas.js
.
import { takeLatest, call, put, all } from 'redux-saga/effects';
import history from '~/services/history';
import api from '~/services/api';
import { signInSuccess } from './actions';
export function* signIn({ payload }) {
const { email, password } = payload;
const response = yield call(api.post, 'sessions', { email, password });
const { token, user } = response.data;
yield put(signInSuccess(token, user));
history.push('/students');
}
export default all([takeLatest('@auth/SIGN_IN_REQUEST', signIn)]);
Vamos acessar o componente de signIn
, e vamos aplicar as seguintes configurações, fazer com que a função handleSubmit
chame a action de signInRequest
.
import React from 'react';
import { useDispatch } from 'react-redux';
import { Form, Input } from '@rocketseat/unform';
import * as Yup from 'yup';
import logo from '~/assets/images/logo.png';
import { signInRequest } from '~/store/modules/auth/actions';
const schema = Yup.object().shape({
email: Yup.string()
.email('Digite um email válido')
.required('O email é obrigatório'),
password: Yup.string().required('A senha é obrigatória'),
});
export default function SignIn() {
const dispatch = useDispatch();
function handleSubmit({ email, password }) {
dispatch(signInRequest(email, password));
}
return (
<>
<img src={logo} alt="gympoint" />
<Form schema={schema} onSubmit={handleSubmit} action="">
<Input name="email" type="email" placeholder="seu e-mail" />
<Input
name="password"
type="password"
placeholder="Sua Senha secreta"
/>
<button type="submit">Acessar</button>
</Form>
</>
);
}
Agora, vamo ditar o reducer
que vai gravar as informações quando nossa request for bem sucedida:
import produce from 'immer';
const INITIAL_STATE = {
token: null,
signed: false,
loading: false,
};
export default function auth(state = INITIAL_STATE, action) {
switch (action.type) {
case '@auth/SIGN_IN_SUCCESS':
return produce(state, draft => {
draft.token = action.payload.token;
draft.signed = true;
});
default:
return state;
}
}
// src/store/modules/auth/reducer.js
Aqui no nosso reducer, ouvimos a ação de Success que foi disparada depois que a request foi bem sucedida, e fizemos mudanças no nosso STATE utilizando a function produce
da lib immer
.
Agora, vamos fazer com que nosso componente Route
, escute as mudanças do state do nosso reducer
de auth
.
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Redirect } from 'react-router-dom';
import AuthLayout from '../pages/_layouts/auth';
import DefautLayout from '../pages/_layouts/default';
import store from '~/store'; // importamos o store
export default function RouteWrapper({
component: Component,
isPrivate,
...rest
}) {
const { signed } = store.getState().auth; // aqui pegamos a informação signed de dentro do nosso resducer auth.
// ...
Agora, vamos configurar nosso module de user
, onde vamos salvar os dados de usuário.
mkdir src/store/modules/user && touch src/store/modules/user/actions.js src/store/modules/user/reducer.js src/store/modules/user/sagas.js
Agora, vamos configurar os arquivos src/store/modules/user/reducer.js
& src/store/modules/user/sagas.js
.
import produce from 'immer';
const INITIAL_STATE = {
profile: null,
};
export default function user(state = INITIAL_STATE, action) {
switch (action.type) {
case '@auth/SIGN_IN_SUCCESS':
return produce(state, draft => {
draft.profile = action.payload.user;
});
default:
return state;
}
}
// src/store/modules/user/reducer.js
Não se esqueça de importar este arquivo dentro do
src/store/modules/rootReducer.js
.
import { all } from 'redux-saga/effects';
export default all([]);
// src/store/modules/user/sagas.js
Não se esqueça de importar este arquivo dentro do
src/store/modules/rootSaga.js
.
Agora, para observar estes dados, dentro do Reactotron
crie um state pra monitorar o estado do reducer de user.
É sempre interessante, para cada
reducer
criar umstate
para monitorar os dados dentro doReactotron
.
Agora vamos persistir os dados que salvamos nos nosso reducers
, instale a seguinte lib redux-persist
yarn add redux-persist
Dentro de src/store
vamos criar um arquivo para a configuração dos dados que irão persistir na aplicação.
touch src/store/persistReducers.js
Agora vamos para a configuração desse arquivo que acabos de criar.
import storage from 'redux-persist/lib/storage';
import { persistReducer } from 'redux-persist';
export default reducers => {
const persistedReducer = persistReducer(
{
key: 'gympoint',
storage,
whitelist: ['auth', 'user'], // passamos os reducers que devem ter os dados persistidos
},
reducers
);
return persistedReducer;
};
Agora, vamos configurar o redux persist na nossa aplicação, para que as informações sejam refletidas e persistidas nela por completo.
import { persistStore } from 'redux-persist'; // new
import createSagaMiddleware from 'redux-saga';
import createStore from './createStore';
import persistReducers from './persistReducers'; // new
import rootReducer from './modules/rootReducer';
import sagas from './modules/rootSaga';
const middlewares = [];
const sagaMonitor =
process.env.NODE_ENV === 'development'
? console.tron.createSagaMonitor()
: null;
const sagaMiddleware = createSagaMiddleware({ sagaMonitor });
middlewares.push(sagaMiddleware);
const store = createStore(persistReducers(rootReducer), middlewares); // new
const persistor = persistStore(store); // new
sagaMiddleware.run(sagas);
export { store, persistor }; // new
// src/store/index.js
Agora, em temos que mudar os lugares em nossa aplicação que importamos o store
.
Edite o arquivo src/App.js
.
import React from 'react';
import { PersistGate } from 'redux-persist/integration/react'; // importe o persistor
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import './config/ReactotronConfig';
import { store, persistor } from './store'; // importe as duas variaveis
import Routes from './routes';
import history from './services/history';
import GlobalStyle from './styles/global';
function App() {
return (
<Provider store={store}>
{/* aqui passamos o persistgate por dentro do store e por fora do resto da aplicação */}
<PersistGate persistor={persistor}>
<Router history={history}>
<Routes />
<GlobalStyle />
</Router>
</PersistGate>
</Provider>
);
}
export default App;
E também o arquivo src/routes/Route.js
.
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Redirect } from 'react-router-dom';
import AuthLayout from '../pages/_layouts/auth';
import DefautLayout from '../pages/_layouts/default';
import { store } from '~/store'; // mudança na importação
// ...
Com isso finalizamos a persistência de dados da nosa aplicação.
Edite o arquivo src/store/modules/auth/actions.js
.
import produce from 'immer';
const INITIAL_STATE = {
token: null,
signed: false,
loading: false,
};
export default function auth(state = INITIAL_STATE, action) {
return produce(state, draft => {
switch (action.type) {
case '@auth/SIGN_IN_REQUEST': {
draft.laoding = true;
break;
}
case '@auth/SIGN_IN_SUCCESS': {
draft.token = action.payload.token;
draft.signed = true;
draft.loading = false;
break;
}
case '@auth/SIGN_FAILURE': {
draft.profile = action.payload.user;
draft.loading = false;
break;
}
default:
}
});
}
Agotra vamos instalar um LIB pra lidar com erros e eibi-los de uma forma mais agradável para o usuário.
yarn add react-toastify
Agora com a lib instalada, dentro de src/App.js
, coloque a seguinte configuração.
import React from 'react';
import { ToastContainer } from 'react-toastify'; // new
import { PersistGate } from 'redux-persist/integration/react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import './config/ReactotronConfig';
import { store, persistor } from './store'; // deve vir depois da configuração do reactotron
import Routes from './routes';
import history from './services/history';
import GlobalStyle from './styles/global';
function App() {
return (
<Provider store={store}>
<PersistGate persistor={persistor}>
<Router history={history}>
<Routes />
<GlobalStyle />
<ToastContainer autoClose={3000} /> {/* new */}
</Router>
</PersistGate>
</Provider>
);
}
export default App;
Agora vamos fazer umas pequenas mudanças no saga
de autenticação.
import { takeLatest, call, put, all } from 'redux-saga/effects';
import { toast } from 'react-toastify'; // new
import history from '~/services/history';
import api from '~/services/api';
import { signInSuccess, signFailure } from './actions';
export function* signIn({ payload }) {
try {
const { email, password } = payload;
// console.tron.log('entrou na SignIN');
const response = yield call(api.post, 'sessions', { email, password });
// console.tron.log('chamou a API');
const { token, user } = response.data;
yield put(signInSuccess(token, user));
history.push('/students');
} catch (error) {
toast.error('Falha na autenticação!'); // new
yield put(signFailure());
}
}
export default all([takeLatest('@auth/SIGN_IN_REQUEST', signIn)]);
// src/store/modules/auth/sagas.js
Coloquei um try/catch
por volta da request, qualquer erro ele cai no catch e exibe o erro :)
Vamos configurar o axios pra armazenar o token, para que todas as requests feitas pelo client sejam automaticamente autenticadas sem que eu me preocupe em recuperar o token e passar toda vez pelas requests.
Dentro do saga de autenticação, faça as seguintes configurações.
import { takeLatest, call, put, all } from 'redux-saga/effects';
import { toast } from 'react-toastify';
import history from '~/services/history';
import api from '~/services/api';
import { signInSuccess, signFailure } from './actions';
export function* signIn({ payload }) {
try {
const { email, password } = payload;
// console.tron.log('entrou na SignIN');
const response = yield call(api.post, 'sessions', { email, password });
// console.tron.log('chamou a API');
const { token, user } = response.data;
yield put(signInSuccess(token, user));
history.push('/students');
} catch (error) {
toast.error('Falha na autenticação!');
yield put(signFailure());
}
}
export function setToken({ payload }) {
if (!payload) return;
const { token } = payload.auth;
if (token) api.defaults.headers.Authorization = `Bearer ${token}`;
}
export default all([
takeLatest('@auth/SIGN_IN_REQUEST', signIn),
takeLatest('persist/REHYDRATE', setToken),
]);
Eu acessei a propriedade defaults
do axios, e dentro dos headers
, setei o Authorization
com o meu token.