I have a login page (login.js), when the form is submited data gets sent using redux , dispatch but when i console.log user selector it shows undefined and i don't really know why. The ajax call work and resp.data.json show the data in saga.js.
Init state is defined so even before the ajax call i should be able to access to isLoading and errorMessage which i cannot even have access.
login.js
import React, { Component, useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { useParallax } from "react-scroll-parallax";
import { useTranslation } from "react-i18next";
import axios from "axios";
import "./Login.css";
import logoWhite from './img/logo-white.png';
import svgLogo from './img/log.png';
import { Helmet } from 'react-helmet';
import { useDispatch, useSelector } from 'react-redux';
import userActions from './redux/auth/actions';
const {
postToApi,
} = userActions;
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const user = useSelector(
state => state.user
);
const dispatch = useDispatch();
const handleLogin = e => {
e.preventDefault();
var data = {};
data.email = email;
data.password = password;
dispatch(postToApi(data));
console.log(user); // Undefined
};
const { t, i18n } = useTranslation();
return (
<div className="login-page">
<Helmet>
<title>Espace Client - Développement de site internet à Antibes, Cannes, Nice</title>
</Helmet>
<div className="container">
<div className="forms-container">
<div className="signin-signup">
<form onSubmit={handleLogin} autoComplete="off" className="sign-in-form">
<h2 className="title">Identifiez-vous</h2>
<div className="input-field">
<i className="fas fa-user"></i>
<input type="text" value={email} onInput={e => setEmail(e.target.value)} placeholder="Adresse e-mail" autoComplete="off" />
</div>
<div className="input-field">
<i className="fas fa-lock"></i>
<input type="password" value={password} onInput={e => setPassword(e.target.value)} placeholder="Mot de passe" autoComplete="new-password" />
</div>
<button type="submit" className="btn solid">CONTINUER</button>
</form>
</div>
</div>
<div className="panels-container">
<div className="panel left-panel">
<div className="content">
<Link to="/"><img src={logoWhite} className="logo" alt="Création de sites Web vitrine" /></Link>
<h3 className="text-white">ESPACE CLIENT</h3>
<p>
Vous pourrez suivre vos factures, modifier vos informations personelles, trouver de l'aide...
</p>
</div>
<img src={svgLogo} className="image" alt="Création de sites Web vitrine" />
</div>
</div>
</div>
</div>
)
}
export default Login;
actions.js
const actions = {
POST_TO_API: 'POST_TO_API',
POST_TO_API_SUCCESS: 'POST_TO_API_SUCCESS',
POST_TO_API_ERROR: 'POST_TO_API_ERROR',
postToApi: data => {
return {
type: actions.POST_TO_API,
payload: { data },
};
},
postToApiSuccess: data => ({
type: actions.POST_TO_API_SUCCESS,
payload: { data },
}),
postToApiError: error => ({
type: actions.POST_TO_API_ERROR,
payload: { error },
}),
};
export default actions;
reducer.js
import actions from './actions';
const initState = {
isLoading: false,
errorMessage: false,
data: []
};
export default function reducer(
state = initState,
{ type, payload }
) {
switch (type) {
case actions.POST_TO_API:
return {
...state,
isLoading: true,
errorMessage: false
};
case actions.POST_TO_API_SUCCESS:
return {
...state,
isLoading: false,
data: payload.data,
errorMessage: false
};
case actions.POST_TO_API_ERROR:
return {
...state,
isLoading: false,
errorMessage: 'There is a problem'
};
default:
return state;
}
}
saga.js
import { all, takeEvery, put, call } from 'redux-saga/effects';
import actions from './actions';
import axios from "axios";
function run(data){
var actionUrl = '/pages/login';
return axios ({
method: 'POST',
url: 'http://xzy/api' + actionUrl,
data: {
data
},
headers: { 'Content-Type': 'application/json;charset=UTF-8', "Access-Control-Allow-Origin": "*", "Accept": "application/json" }
});
}
function* postToApi(payload) {
try {
const resp = yield call(run, payload.payload.data)
console.log(resp.data.json); // Data shown in the log
yield put(actions.postToApiSuccess(resp.data.json));
} catch (error) {
yield put(actions.postToApiError(error));
}
}
export default function* rootSaga() {
yield all([
takeEvery(actions.POST_TO_API, postToApi),
]);
}
App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './redux/store';
import Routes from './router';
import AppProvider from './AppProvider';
const App = () => (
<Provider store={store}>
<AppProvider>
<>
<Routes />
</>
</AppProvider>
</Provider>
);
export default App;
AppProvider.js
import React from 'react';
import { ConfigProvider } from 'antd';
export default function AppProvider({ children }) {
return (
<ConfigProvider locale="fr">
{children}
</ConfigProvider>
);
}
./redux/store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './root-reducer';
import rootSaga from './root-saga';
const sagaMiddleware = createSagaMiddleware();
const middlewares = [thunk, sagaMiddleware];
const bindMiddleware = middleware => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const store = createStore(rootReducer, bindMiddleware(middlewares));
sagaMiddleware.run(rootSaga);
export { store };
root-saga.js
import { all } from 'redux-saga/effects';
import authSagas from './auth/saga';
export default function* rootSaga(getState) {
yield all([
authSagas()
]);
}
root-reducer.js
import { combineReducers } from 'redux';
import App from './app/reducer';
import Auth from './auth/reducer';
export default combineReducers({
Auth,
App
});
Related
Ilm going through the "The Ultimate Authentication course with C# and React" on Udemy and got to the using redux lesson and the /users and the refresh endpoint should be only loading once but it is still loading multiple times as you can see in screenshot
screenshot here
It should only be loading the users and refresh once based on the instructors' instructions. here is my code:
note: I am using the redux and the redux toolkit.
index.tsx
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import './Interceptors/axios';
import {store} from './redux/store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
reportWebVitals();
axios.ts
import axios from "axios";
axios.defaults.baseURL = `${process.env.REACT_APP_API_URL}`
let refresh = false;
axios.interceptors.response.use(response => response, async error => {
if(error.response.status === 401 && !refresh){
refresh = true;
const response = await axios.post('/Users/refresh', {}, {withCredentials: true})
if(response.status === 200){
axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`
return axios(error.config)
}
}
refresh = false;
return error;
});
store.ts
import {configureStore} from '#reduxjs/toolkit';
import authReducer from './authSlice'
export const store = configureStore({
reducer: {
auth: authReducer
}
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
authSlice.ts
import {createSlice} from '#reduxjs/toolkit';
const initialState = {
value: false
}
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setAuth: (state, action) => {
state.value = action.payload;
}
}
})
export const {setAuth} = authSlice.actions;
export default authSlice.reducer;
login.tsx
import React, { useState } from 'react';
import Button from 'react-bootstrap/Button';
import {ILogin} from './Interfaces/Interfaces';
import {useNavigate} from 'react-router-dom';
import axios from 'axios';
export const Login = () => {
const [login, setLogin] = useState<Partial<ILogin>>({});
async function handleForm(arg:any) {
try {
arg.preventDefault();
console.log(JSON.stringify({ ...login }))
const endpoint = '/Users/Login';
const data = {
...login
};
const config = {
withCredentials: true,
//Credentials: true,
headers: {
"Content-Type": "application/json",
}
};
try {
const response = await axios.post(endpoint, data, config);
if (response.status === 200) {
axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`
}
} catch (error) {
console.log(error);
}
loggedIn(true);
}
catch (err) {
console.log(err);
}
}
const navigate = useNavigate();
const loggedIn = (redirect:boolean) => {
if(redirect)
{
navigate('/home');
}
}
return <main className="form-signin" style={{'width': '300px', 'margin': '0 auto'}}>
<form>
<h1 className="h3 mb-3 fw-normal">Please sign in</h1>
<div className="form-floating">
<input type="text" className="form-control" id="floatingUserName" autoComplete="User Name" placeholder="User123" value={login.UserName || ''} onChange={e => setLogin({ ...login, UserName: e.target.value})}/>
<label htmlFor="floatingInput">User Name</label>
</div>
<div className="form-floating">
<input type="password" className="form-control" id="floatingPassword" autoComplete='current-password' placeholder="Password" value={login.Password || ''} onChange={e => setLogin({ ...login, Password: e.target.value})} />
<label htmlFor="floatingPassword">Password</label>
</div>
<div className="checkbox mb-3">
<label>Remember me </label>
<input type="checkbox" value="remember-me" />
</div>
<Button className="w-100 btn btn-lg btn-primary" type="submit" onClick={e => {
handleForm(e)
}}>Sign in</Button>
</form>
</main>
}
siteNav.tsx
import axios from 'axios';
//import { useEffect, useState } from 'react';
import Button from 'react-bootstrap/Button';
import Container from 'react-bootstrap/Container';
//import Form from 'react-bootstrap/Form';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
//import NavDropdown from 'react-bootstrap/NavDropdown';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../redux/store';
import {setAuth} from '../redux/authSlice';
export const SiteNav = () => {
const auth = useSelector((state:RootState) => state.auth.value);
const dispatch = useDispatch();
const logout = async () => {
await axios.post('/Users/Logout', {}, {withCredentials: true});
axios.defaults.headers.common['Authorization'] = ``;
dispatch(setAuth(false));
}
let links;
if(auth){
// handle logout
links = <div className="text-end">
<Button href="/login" className="btn me-2" style={{'color':'white'}} variant="outline-dark" onClick={logout}>Logout</Button>
</div>
}
else
{
//handle login
links = <div className="text-end">
<Button href="/login" className="btn me-2" style={{'color':'white'}} variant="outline-dark">Sign in</Button>
<Button href="/register" className="btn me-2" style={{'color':'white'}} variant="outline-dark">Register</Button>
</div>
}
return (
<Navbar bg="dark" variant="dark" expand="lg">
<Container fluid>
<Navbar.Brand className="light" href="/home"><img src="logo192.png" width="62" height="62" alt="SCSFC"></img> SoCal SportFishing Club</Navbar.Brand>
<Navbar.Toggle aria-controls="navbarScroll" />
<Navbar.Collapse id="navbarScroll">
<Nav
className="me-auto my-2 my-lg-0"
style={{ maxHeight: '100px' }}
navbarScroll
>
<Nav.Link href="#action1">About</Nav.Link>
<Nav.Link href="#action1">Meetings</Nav.Link>
<Nav.Link href="#action2">Charters</Nav.Link>
<Nav.Link href="#action2">Landings</Nav.Link>
</Nav>
{links}
</Navbar.Collapse>
</Container>
</Navbar>
);
}
home.tsx
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setAuth } from '../redux/authSlice';
import { RootState } from '../redux/store';
export const Home = () => {
const dispatch = useDispatch()
const auth = useSelector((state:RootState) => state.auth.value);
const [message, setMessage] = useState('')
useEffect(() => {
(async () => {
try{
const {data} = await axios.get(`${process.env.REACT_APP_API_URL}/users`)
setMessage(`Hi ${data.userName}`)
dispatch(setAuth(true));
}
catch (e){
setMessage('You are not logged in.');
dispatch(setAuth(false));
}
})()
}, []);
return (<div className="container mt-5 text-center">
<h3>{auth ? message : 'You are not authenticated'}</h3>
</div>);
}
I was expecting to have the users and refresh only run once on load.
I am getting this error and I don;t know what else to do.
I am using next.js and my code looks like this.
The _app.js:
import '../styles/globals.scss'
import React from 'react'
import Layout from '../components/Layout'
import Head from "next/head";
import Signin from "./signin";
import Register from "./register";
import { DataProvider } from "../store/GlobalState";
function MyApp ({
Component,
pageProps
}) {
if (typeof window !== 'undefined') {
if (window.location.pathname === '/signin') {
return (
<DataProvider>
<Signin/>
</DataProvider>
)
} else if (window.location.pathname === '/register') {
return (
<DataProvider>
<Register/>
</DataProvider>
)
}
}
return (
<DataProvider>
<Head>
<title>Above the Sky</title>
</Head>
<Layout>
<Component {...pageProps} />
</Layout>
</DataProvider>
)
}
export default MyApp
I am doing this because I want the register and the login pages to be separate from the layout, not having any header or footer whatsoever... If you have a hint on this , how I should do this better please tell me .... but this is not the main problem..
and now the Register.js:
import Head from 'next/head'
import { useContext, useEffect, useState } from "react";
import Link from 'next/link'
import valid from '../utils/valid'
import { DataContext } from "../store/GlobalState";
const Register = () => {
const [ mounted, setMounted ] = useState(false);
const initialState = {
email: '',
password: '',
cf_password: ''
};
const [ userData, setUserData ] = useState(initialState);
const {
email,
password,
cf_password
} = userData;
const {
state,
dispatch
} = useContext(DataContext)
const handleChangeInput = e => {
const {
name,
value
} = e.target
setUserData({
...userData,
[name]: value
})
dispatch({
type: 'NOTIFY',
payload: {}
})
}
const handleSubmit = async e => {
e.preventDefault()
const errorMessage = valid(email, password, cf_password)
if (errorMessage) {
return dispatch({
type: 'NOTIFY',
payload: { error: errorMessage }
})
}
dispatch({
type: 'NOTIFY',
payload: { success: 'Ok' }
})
}
useEffect(() => {
setMounted(true)
}, [])
return (
mounted
&&
<div style={{
backgroundColor: 'black',
height: '100vh'
}}>
<Head>
<title>Register Page</title>
</Head>
<div className="login-dark" style={{ height: "695px" }}>
<form className='container' onSubmit={handleSubmit}>
<div className="illustration"><i className="fas fa-thin fa-user-plus"/></div>
<div className="mb-3">
<label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
<input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
name="email" value={email} onChange={handleChangeInput}/>
<div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword1" className="form-label">Password</label>
<input type="password" className="form-control" id="exampleInputPassword1"
name="password" value={password} onChange={handleChangeInput}/>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword2" className="form-label">Confirm Password</label>
<input type="password" className="form-control" id="exampleInputPassword2"
name="cf_password" value={cf_password} onChange={handleChangeInput}/>
</div>
<div className='button-container'>
<button type="submit" className="btn btn-primary btn-block">Register</button>
</div>
<a className="forgot" href="#">Forgot your email or password?</a>
<p className="have-account">Already have an account ? <Link href="/signin"><a style={{ color: 'crimson' }}>Login here</a></Link></p>
</form>
</div>
</div>
)
}
export default Register
When I render the register page I get this error in the console ..
"next-dev.js?3515:32 Warning: Did not expect server HTML to contain a in ."
These are my store files aswell:
Actions.js
export const ACTIONS = {
NOTIFY: 'NOTIFY',
AUTH: 'AUTH'
}
Reducer.js
import { ACTIONS } from './Actions';
const reducers = (state, action) => {
switch (action.type) {
case ACTIONS.NOTIFY:
return {
...state,
notify: action.payload
};
case ACTIONS.AUTH:
return {
...state,
auth: action.payload
};
default:
return state;
}
}
export default reducers
and the GlobalState.js
import { createContext, useReducer } from "react";
import reducers from "./Reducers";
export const DataContext = createContext()
export const DataProvider = ({ children }) => {
const initialState = {
notify: {},
auth: {}
}
const [ state, dispatch ] = useReducer(reducers, initialState)
const { cart, auth } = state
return (
<DataContext.Provider value={{
state,
dispatch
}}>
{children}
</DataContext.Provider>
)
}
I have a ecommerce store with registration and login. After registration, the token is stored in cookie and authentication state is updated. It is working. But the problem is, when I refresh the page, authentication state is set to null. Please check my store and reducers.
store.js
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState,
composeWithDevTools(applyMiddleware(...middleware)));
export default store;
authReducer.js
import * as types from '../types'
export const authReducer = (state = { token: null }, action) => {
switch (action.type) {
case types.AUTHENTICATE:
return {
...state,
token: action.payload
};
case types.DEAUTHENTICATE:
return {
token: null
};
default:
return state;
}
};
authAction.js
import * as types from '../types'
import axios from 'axios'
import cookie from 'js-cookie';
import * as api from '../../pages/api'
import Router from 'next/router';
export const authenticate = user => async dispatch => {
const res = await axios.post(api.URL_REGISTER, {user})
.then(res => {
if (res.data.response === 200) {
setCookie('token', res.data.data.token);
Router.push('/');
dispatch({
type: types.AUTHENTICATE,
payload: res.data.data.token
})
}
else
dispatch({
type: types.AUTHENTICATE,
payload: res.data
})
}).catch(error => {
console.log(error);
});
}
// gets the token from the cookie and saves it in the store
export const reauthenticate = token => {
return dispatch => {
dispatch({ type: types.AUTHENTICATE, payload: token });
};
};
// removing the token
export const deauthenticate = () => {
return dispatch => {
removeCookie('token');
Router.push('/');
dispatch({ type: types.DEAUTHENTICATE });
};
};
/**
* cookie helper methods
*/
export const setCookie = (key, value) => {
if (process.browser) {
cookie.set(key, value, {
expires: 1,
path: '/'
});
}
};
export const removeCookie = key => {
if (process.browser) {
cookie.remove(key, {
expires: 1
});
}
};
export const getCookie = key => {
return cookie.get(key);
};
Header.js
import React from 'react'
import Link from 'next/link'
import {FontAwesomeIcon} from '#fortawesome/react-fontawesome'
import { faSearch, faShoppingCart, faUserCircle, faBoxOpen, faHeart } from '#fortawesome/fontawesome-free-solid'
import { deauthenticate } from '../../store/actions/authAction';
import { connect } from 'react-redux';
const Header = ({ deauthenticate, isAuthenticated }) => (
<div>
<div className="col-12 col-md-4 col-lg-3">
<div className="text-center text-md-right">
<div className="d-inline loginDrop">
<Link href="/">
<a className="signinBtn mr-5">{!isAuthenticated ? "Sign In" : "My Account"}</a>
</Link>
<div className={!isAuthenticated ? "login-content" : "login-content logout-content"}>
<p> </p>
<div className="login-inner">
<Link href={!isAuthenticated ? "/login" : "/profile"}><a><FontAwesomeIcon icon={faUserCircle} className="mr-2"/> Your Profile</a></Link>
<Link href={!isAuthenticated ? "/login" : "/orders"}><a><FontAwesomeIcon icon={faBoxOpen} className="mr-2 orderIcon"/> Orders</a></Link>
<Link href={!isAuthenticated ? "/login" : "/wishlist"}><a><FontAwesomeIcon icon={faHeart} className="mr-2"/> Whishlist</a></Link>
<div className="otherDrop">
{!isAuthenticated ?
<>
<p className="first">Don't have an account?</p>
<p className="register"><Link href="/register" as="/register"><a>Register</a></Link></p>
<p className="login"><Link href="/login"><a>Login</a></Link></p>
</>
:
<p className="login"><a href="#" onClick={deauthenticate}>Logout</a></p>
}
</div>
</div>
</div>
</div>
<Link href="/">
<a className="cartBtn"><FontAwesomeIcon icon={faShoppingCart} className="mr-xl-1"/> Cart</a>
</Link>
</div>
</div>
</div>
)
const mapStateToProps = state => ({ isAuthenticated: !!state.authentication.token });
export default connect(
mapStateToProps,
{ deauthenticate }
)(Header);
_app.js
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import {createWrapper} from 'next-redux-wrapper'
import store from '../store/store'
class MyApp extends App {
render() {
const {Component, pageProps} = this.props
return (
<Provider store={store}>
<Component {...pageProps}></Component>
</Provider>
)
}
}
const makestore = () => store;
const wrapper = createWrapper(makestore);
export default wrapper.withRedux(MyApp);
How to fix initial state not to be null even after refresh the page. I am really stuck here. Is there any option to fix.
All you need is to persist your redux state across a browser refresh by using redux middleware like redux-persist, ie:
if (isClient) {
store = createStore(
persistReducer(persistConfig, rootReducer),
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
}
I have been trying to create a React app using redux and redux-saga, but I haven't been able to make it work, and I alway get the value of undefined as the result.
This is my component catalogs/index.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import CatalogHeader from './CatalogHeader';
import CircularProgress from '#material-ui/core/CircularProgress';
import {getCatalogs} from 'actions/Catalogs';
import IntlMessages from 'util/IntlMessages';
import CustomScrollbars from 'util/CustomScrollbars';
class Catalogs extends Component {
constructor() {
super();
this.state = {
anchorEl: null
}
}
updateSearch = (evt) => {
this.props.updateSearch(evt.target.value);
this.onSearchTodo(evt.target.value)
};
render() {
const {catalogsList} = this.props;
return (
<div className="app-wrapper">
<div className="animated slideInUpTiny animation-duration-3">
<div className="app-module">
<div className="module-box">
<div className="module-box-header">
<CatalogHeader catalogsList={catalogsList}
placeholder="Buscar" user={this.props.user}
onChange={this.updateSearch.bind(this)}
value={this.props.searchTodo}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = ({catalogs, settings}) => {
const {width} = settings;
const {catalogsList} = catalogs;
return {
width,
catalogsList,
}
};
export default connect(mapStateToProps, {
getCatalogs,
})(Catalogs);
This is the other component catalogs/CatalogHeader.js
import React from 'react';
import IconButton from '#material-ui/core/IconButton';
import Button from '#material-ui/core/Button';
import {Dropdown, DropdownMenu, DropdownToggle, Popover} from 'reactstrap';
import SearchBox from 'components/SearchBox';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import FormHelperText from '#material-ui/core/FormHelperText';
import Select from '#material-ui/core/Select';
class CatalogHeader extends React.Component {
handleChange = name => event => {
this.setState({[name]: event.target.value});
};
onSearchBoxSelect = () => {
this.setState({
searchBox: !this.state.searchBox
})
};
constructor() {
super();
this.state = {
anchorEl: undefined,
searchBox: false,
popoverOpen: false
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
popoverOpen: !this.state.popoverOpen
});
}
printCatalogs(catalogs) {
console.log(catalogs);
}
render() {
const {catalogs} = this.props;
return (
<div className="module-box-header-inner catalogs-header">
<div className="col-5 catalogs-header">
<FormControl className="w-100 mb-2">
{this.printCatalogs(this.props.catalogs)}
<InputLabel htmlFor="age-simple">Seleccione Catálogo</InputLabel>
<Select
value={this.state.age}
onChange={this.handleChange('age')}
input={<Input id="ageSimple1"/>}>
{catalogs.map(catalog =>
<MenuItem key={catalog}>
{catalog}
</MenuItem>,
)}
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</div>
<div className="module-box-header-right col-7 catalogs-header">
<div className="search-bar right-side-icon bg-transparent d-none d-sm-block col-6">
<div className="form-group">
<input className="form-control border-0" type="search" placeholder="Buscar"/>
<button className="search-icon"><i className="zmdi zmdi-search zmdi-hc-lg"/></button>
</div>
</div>
<div className="col-6">
<div className="catalogs-header catalogs-header-buttons">
<Button variant="contained" className="jr-btn bg-white">
<i className="zmdi zmdi-filter-list zmdi-hc-fw"/>
<span>Filtrar</span>
</Button>
<Button variant="contained" className="jr-btn bg-white">
<i className="zmdi zmdi-sort zmdi-hc-fw"/>
<span>Ordenar</span>
</Button>
</div>
</div>
</div>
</div>
)
}
}
export default CatalogHeader;
This is my actions file:
import {
GET_CATALOGS,
GET_CATALOGS_SUCCESS,
SHOW_MESSAGE
} from 'constants/ActionTypes';
export const getCatalogs = (group) => {
return {
type: GET_CATALOGS,
payload: group
};
};
export const getCatalogsSuccess = (catalogs) => {
return {
type: GET_CATALOGS_SUCCESS,
payload: catalogs
}
};
export const showCatalogsMessage = (message) => {
return {
type: SHOW_MESSAGE,
payload: message
};
};
This is my reducers file:
import {
GET_CATALOGS,
SHOW_MESSAGE,
HIDE_MESSAGE,
GET_CATALOGS_SUCCESS
} from 'constants/ActionTypes';
const INIT_STATE = {
loader: false,
alertMessage: '',
showMessage: false,
catalogsList: null
};
export default (state=INIT_STATE, action) => {
switch (action.type) {
case GET_CATALOGS_SUCCESS: {
return {
...state,
loader: false,
catalogsList: action.payload
}
}
case SHOW_MESSAGE: {
return {
...state,
alertMessage: action.payload,
showMessage: true,
loader: false
}
}
case HIDE_MESSAGE: {
return {
...state,
alertMessage: '',
showMessage: false,
loader: false
}
}
default:
return state;
}
}
This is my sagas file:
import {all, call, fork, put, takeEvery} from "redux-saga/effects";
import {catalogs} from 'backend/Catalogs';
import {GET_CATALOGS} from "constants/ActionTypes";
import {getCatalogs, getCatalogsSuccess, showCatalogsMessage} from 'actions/Catalogs';
const getCatalogsRequest = async (group) => {
await catalogs.getCatalogs(group)
.then(catalogsList => catalogsList)
.catch(error => error);
}
function* getCatalogsListFromRequest({payload}) {
const {group} = payload;
try {
const catalogsList = yield call(getCatalogsRequest, group);
if (catalogsList.message) {
yield put(showCatalogsMessage(catalogsList.Message));
} else {
yield put(getCatalogsSuccess(catalogsList.catalogsList))
}
} catch (error) {
yield put(showCatalogsMessage(error));
}
}
export function* getCatalogsList() {
yield takeEvery(GET_CATALOGS, getCatalogsListFromRequest);
}
export default function* rootSaga() {
yield all([
fork(getCatalogsList)
]);
}
And this is the file that performs the ajax request through Axios (I know for sure that this code is never reached, and a request to the backend server is never performed):
import axios from 'axios';
import {backendServer} from './constants';
const getCatalogsEndpoint = backendServer + 'api/util/catalogs/';
const getCatalogs = (group) => {
console.log('here inside getCatalogs in backend/Catalogs');
return axios.get(getCatalogsEndpoint+(group=null)?null:'?group='+group)
.then(Response => {
return {catalogsList: Response.data}
})
.catch(Error => Error);
};
export const catalogs = {
getCatalogs: getCatalogs
}
The problem arises in CatalogsHeader.js, because this.props.catalogsList always has the value of undefined:
I see problem in CatalogHeader. I think, you are referring a wrong pop.
In index.js you are passing the prop as catalogsList={catalogsList} to CatalogHeader.
But in CatalogHeader you are accessing prop as const {catalogs} = this.props; i.e. catalogs which is undefined. Please try changing this into catalogsList.
There is a Login component
// #flow
import type {
TState as TAuth,
} from '../redux';
import * as React from 'react';
import Button from '#material-ui/core/Button';
import FormControl from '#material-ui/core/FormControl';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import Paper from '#material-ui/core/Paper';
import { withNamespaces } from 'react-i18next';
import { Link } from 'react-router-dom';
import {
connect,
} from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import useStyles from './styles';
import { login } from '../../redux';
import { push } from 'connected-react-router';
const logo = './assets/images/logo.png';
const {
useEffect,
} = React;
type TInputProps = {
input: Object,
meta: {
submitting: boolean,
}
}
const UserNameInput = (props: TInputProps) => (
<Input
id="userName"
name="userName"
autoComplete="userName"
autoFocus
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
const PasswordInput = (props: TInputProps) => (
<Input
name="password"
type="password"
id="password"
autoComplete="current-password"
{...props}
{...props.input}
disabled={props.meta.submitting}
/>
);
type TProps = {
t: Function,
login: Function,
handleSubmit: Function,
error: string,
submitting: boolean,
auth: TAuth,
}
// TODO: fix flow error inside
const Login = ({
t,
login,
handleSubmit,
error,
submitting,
auth,
}: TProps) => {
const classes = useStyles();
useEffect(() => {
if (auth) {
console.log('push', push);
push('/dashboard');
}
}, [auth]);
return (
<main className={classes.main}>
<Paper className={classes.paper}>
<img src={logo} alt="logo" className={classes.logo} />
<form
className={classes.form}
onSubmit={handleSubmit((values) => {
// return here is very important
// login returns a promise
// so redux-form knows if it is in submission or finished
// also important to return because
// when throwing submissionErrors
// redux-form can handle it correctly
return login(values);
})}
>
<FormControl margin="normal" required fullWidth>
<Field
name="userName"
type="text"
component={UserNameInput}
label={
<InputLabel htmlFor="userName">{t('Username')}</InputLabel>
}
/>
</FormControl>
<FormControl margin="normal" required fullWidth>
<Field
name="password"
type="password"
component={PasswordInput}
label={
<InputLabel htmlFor="password">{t('Password')}</InputLabel>
}
/>
</FormControl>
<div className={classes.error}>{error}</div>
<Button
disabled={submitting}
type="submit"
fullWidth
variant="outlined"
color="primary"
className={classes.submit}
>
{t('Sign in')}
</Button>
<Link className={classes.forgot} to="/forgot">
{t('Forgot Password?')}
</Link>
</form>
</Paper>
</main>
);
};
const mapStateToProps = ({ auth }) => ({ auth });
const mapDispatchToProps = {
login,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(
reduxForm({ form: 'login' })(withNamespaces()(Login))
);
In the useEffect hook the push from connected-react-router is used. The hook fires ok but nothing happens after it.
The same way, push is used in login action.
// #flow
import type {
TReducer,
THandlers,
TAction,
TThunkAction,
} from 'shared/utils/reduxHelpers';
import type {
TUser,
} from 'shared/models/User';
import createReducer from 'shared/utils/reduxHelpers';
import urls from 'constants/urls';
import axios, { type $AxiosXHR } from 'axios';
import { SubmissionError } from 'redux-form';
import { push } from 'connected-react-router';
export type TState = ?{
token: string,
result: $ReadOnly<TUser>,
};
export const ON_LOGIN = 'ON_LOGIN';
export const login: ({ userName: string, password: string }) => TThunkAction =
({ userName, password }) => async (dispatch, getState) => {
const res: $AxiosXHR<{username: string, password: string}, TState> =
await axios.post(`${urls.url}/signin`, { username: userName, password })
.catch((err) => {
throw new SubmissionError({ _error: err.response.data.message });
});
const data: TState = res.data;
dispatch({
type: ON_LOGIN,
payload: data,
});
push('/dashboard');
};
const handlers: THandlers<TState, TAction<TState>> = {
[ON_LOGIN]: (state, action) => action.payload,
};
const initialState = null;
const reducer: TReducer<TState> = createReducer(initialState, handlers);
export default reducer;
Here everything goes successful and dispatch happens and there is no push happening again.
Whats the problem?
Shouldn't there be dispatch(push('/dashboard')); ?
You just have to make sure that you do not create your middleware and pass in the history api before calling createRootReducer function.
If you try to create your middleware with routerMiddleware(history) too early , history will be passed in as undefined. Follow the README.md as it explains the exact execution order.
// configureStore.js
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
...
export const history = createBrowserHistory()
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history), // <-- Initiates the History API
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // <--- Now history can be passed to middleware
// ... other middlewares ...
),
),
)
return store
}