I have facing a problem in redux form , my issue is in code, once i pass my server validation. I want to dispatch a action, but i couldn't make it,
i am using redux form's submit validations example approach, in my form i have both the client & server side validation.
LoginForm.js
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import { required, email, maxLength25 } from '../utils/validations';
import validate from '../utils/signup_validate';
import { renderField } from '../utils/textFieldGroup';
import { login } from '../actions/submit';
const LoginForm =(props) => {
const { handleSubmit, submitSucceeded, error } = props
return (
<form onSubmit={handleSubmit(login)}>
<div className={typeof error!='undefined'?'show alert alert-danger': 'hidden'}>
<strong>Error!</strong> {error}
</div>
<Field name="email" type="text"
component={renderField} label="Email"
validate={[ required, email, maxLength25 ]}
/>
<Field name="password" type="password"
component={renderField} label="Password"
validate={[ required, maxLength25 ]}
/>
<p>
<button type="submit" disabled={submitSucceeded} className="btn btn-primary btn-lg">Submit</button>
</p>
</form>
)
}
export default reduxForm({
form: 'loginForm', // a unique identifier for this form
validate
})(LoginForm)
submit.js
import axios from 'axios';
import isEmpty from 'lodash/isEmpty';
import { SubmissionError } from 'redux-form'
import {browserHistory} from 'react-router';
import setAuthorizationsToken from '../utils/setAuthorizationsToken';
import { SET_CURRENT_USER } from '../utils/types';
import jwt from 'jsonwebtoken';
export function login(data){
console.log('submit', data);
return axios.post('api/auth/login', data)
.then((response) => {
console.log('response', response.data.token);
const token = response.data.token
localStorage.setItem('jwtToken', token)
setAuthorizationsToken(token)
store.dispatch(setCurrentUser(jwt.decode(localStorage.jwtToken)))
browserHistory.push('/');
})
.catch((error) => {
console.log('error', error.response);
throw new SubmissionError({ _error: error.response.data.errors});
})
}
function setCurrentUser(user) {
console.log('setCurrentUser');
return {
type: SET_CURRENT_USER,
user: user
}
}
i want to dispatch the setCurrentUser function so that i can set the user data in redux store.
Thanks in advance.
Solution 1
Instead of passing directly your login function to the handleSubmit, you can pass anonther function that does:
login(data).then((user) => dispatch(setCurrentUser(user))
For this to work, you need to use connect from react-redux to allow your component access to the dispatch function of the redux store. Also, you need to return the user from the resolve branch of the ajax call:
.then((response) => {
console.log('response', response.data.token);
const token = response.data.token
localStorage.setItem('jwtToken', token)
setAuthorizationsToken(token)
return jwt.decode(localStorage.jwtToken));
})
Also, my advice is to move the browserHistory.push('/') call to the component (after dispatching the setCurrentUser action)
Solution 2
You actually need to chain actions together, login -> then -> setCurrentUser. For this you should use a redux middleware. If you haven't worked with middlewares before I suggest starting with redux-thunk: https://github.com/gaearon/redux-thunk. With redux-thunk you can use the dispatch function inside your action creators to dispatch multiple actions. If you want more info on this, drop a comment below and I'll try to expand my answer.
Related
DESCRIPTION:
I'm facing while implementing custom login form in my nextjs site using next-auth.
It keeps on redirecting to "https://localhost:3000/api/auth/signin?csrf=true"
SERVER SIDE CODE WRITTEN IN "[...nextauth.js]" FILE INSIDE "pages/api/auth/" folder.
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
const providers = [
CredentialsProvider({
id: 'credentials',
name: 'credentials',
authorize: async (credentials) => {
console.log("AUTHORIZING...",credentials,req);
const res = await fetch(process.env.apiURL+"api/users/authenticate", {
method: 'POST',
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }
});
const user = await res.json();
if (res.ok && user) {
return user;
}
return null;
}
}),
];
const callbacks = {
async signIn({ user, account, profile, email, credentials }) {
console.log("IN CALLBACK HERE!!!");
const isAllowedToSignIn = true
if (isAllowedToSignIn) {
return true
} else {
return false
}
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session(session, token) {
console.log("IN THE CALLBACK SESSION NOW");
session.accessToken = token.accessToken
return session
}
}
const options = {
providers,
callbacks,
secret: process.env.JWT_SECRET,
pages: {
signIn: '/my-account',
signUp: '/signup',
error: '/my-account'
},
debug: true
}
export default (req, res) => NextAuth(req, res, options);
MY CUSTOM LOGIN FORM'S SUBMIT BUTTON ACTION:
const res = await signIn('credentials', { redirect: false, username: user.username, passw: user.passw, callbackUrl: `${window.location.origin}` });
console.log(res);
if (res?.error) {
console.error(res.error);
}
if (res.url) router.push(res.url);
ENVIRONMENT VARIABLES
NEXTAUTH_URL=https://localhost:3000/api/auth
NEXTAUTH_SECRET=PETSFINEST
ERROR IS
the Authorize function of Credentials Provider is not at all being called.. as soon as I press SIGNIN button from my custom login form, it simply redirects to "https://localhost:3000/api/auth/signin?csrf=true" URL and thats it.
Please help guys.. what am I doing wrong here? There's no other details available anywhere, and I've followed the documentation provided on next-auth site.
I ran into this same issue. After banging my head against the wall for ages, it ends up https was being used because I was running it with Vercel...
So rather than using vercel dev I switched it to npx next dev and it resolved the issue for me.
Hey try changing to http on the NEXTAUTH_URL env variable
So change this:
NEXTAUTH_URL=https://localhost:3000/api/auth
into
NEXTAUTH_URL=http://localhost:3000/api/auth
Open the browser inspect tools and check the network tab. Pay attention to the payload on the submit. You should have csrfToken showing a value. Check that email, password and csrf are present in the payload. This helped me solve my issue similar to this when implementing passwordless email form. Here is my custom signin...
import React from 'react'
import {getProviders, signIn} from 'next-auth/react'
import { getCsrfToken } from "next-auth/react"
import styles from '../../styles/signin.module.css'
import Logo from '../../components/Logo'
import Link from 'next/link'
import { GoogleLogin } from '../../components/GoogleLogin'
export default function SignIn ({ csrfToken, providers }) {
console.log(providers.google.signinUrl);
return (
<div className={styles.content}>
<div className={styles.cardWrapper}>
<Logo className={styles.logo}/>
<div className={styles.cardContent}>
<form method="post" action="/api/auth/signin/email">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<input type="email" id="email" name="email" placeholder='Email..' />
<button className={styles.primaryBtn} type="submit">Sign in with Email</button>
</form>
<p className={styles.seperator}> Or </p>
<form method="post" action="/api/auth/signin/google">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<button className={styles.primaryBtn} type="submit">Sign in with Google</button>
</form>
</div>
</div>
</div>
)
}
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context)
const providers = await getProviders(context)
return {
props: { csrfToken, providers },
}
}
I think you can do something similar for credentials as long as you have set it up correctly as per docs in [...nextauth].js.
The code above can be put into seperate components to "Reactify" the code.
Hope this helps.
I've been having the same exact issue and banging my head against the wall for a few hours trying to debug this. The problem for me was that my SMTP server string was still using the 'smtp.example.com' from the docs instead of (in my case) 'smtp.sendgrid.net'.
EMAIL_SERVER="smtp://<user>:<password>#smtp.example.com:587"
Fairly new to redux, react-redux, and redux toolkit, but not new to React, though I am shaky on hooks. I am attempting to dispatch an action from the click of a button, which will update the store with the clicked button's value. I have searched for how to do this high and low, but now I am suspecting I am thinking about the problem in React, without understanding typical redux patterns, because what I expect to be possible is just not done in the examples I have found. What should I be doing instead? The onclick does seem to capture the selection, but it is not being passed to the action. My goal is to show a dynamic list of buttons from data collected from an axios get call to a list of routes. Once a button is clicked, there should be a separate call to an api for data specific to that clicked button's route. Here is an example of what I currently have set up:
reducersRoutes.js
import { createSlice } from "#reduxjs/toolkit";
import { routesApiCallBegan } from "./createActionRoutes";
const slice = createSlice({
name: "routes",
initialState: {
selected: ''
},
{... some more reducers...}
routeSelected: (routes, action) => {
routes.selected = action.payload;
}
},
});
export default slice.reducer;
const { routeSelected } = slice.actions;
const url = '';
export const loadroutes = () => (dispatch) => {
return dispatch(
routesApiCallBegan({
url,
{...}
selected: routeSelected.type,
})
);
};
createActionRoutes.js
import { createAction } from "#reduxjs/toolkit";
{...some other actions...}
export const routeSelected = createAction("routeSelection");
components/routes.js:
import { useDispatch, useSelector } from "react-redux";
import { loadroutes } from "../store/reducersRoutes";
import { useEffect } from "react";
import { routeSelected } from "../store/createActionRoutes";
import Generic from "./generic";
const Routes = () => {
const dispatch = useDispatch();
const routes = useSelector((state) => state.list);
const selected = useSelector((state) => state.selected);
useEffect(() => {
dispatch(loadroutes());
}, [dispatch]);
const sendRouteSelection = (selection) => {
dispatch(routeSelected(selection))
}
return (
<div>
<h1>Available Information:</h1>
<ul>
{routes.map((route, index) => (
<button key={route[index]} className="routeNav" onClick={() => sendRouteSelection(route[0])}>{route[1]}</button>
))}
</ul>
{selected !== '' ? <Generic /> : <span>Data should go here...</span>}
</div>
);
};
export default Routes;
Would be happy to provide additional code if required, thanks!
ETA: To clarify the problem - when the button is clicked, the action is not dispatched and the value does not appear to be passed to the action, even. I would like the selection value on the button to become the routeSelected state value, and then make an api call using the routeSelected value. For the purpose of this question, just getting the action dispatched would be plenty help!
After writing that last comment, I may actually see a couple potential issues:
First, you're currently defining two different action types named routeSelected:
One is in the routes slice, generated by the key routeSelected
The other is in createActionRoutes.js, generated by the call to createAction("routeSelection").
You're importing the second one into the component and dispatching it. However, that is a different action type string name than the one from the slice - it's just 'routeSelection', whereas the one in the slice file is 'routes/routeSelected'. Because of that, the reducer logic in the slice file will never run in response to that action.
I don't think you want to have that separate createAction() call at all. Do export const { routeSelected } = slice.actions in the slice file, and dispatch that action in the component.
I'm also a little concerned about the loadroutes thunk that you have there. I see that you might have omitted some code from the middle, so I don't know all of what it's doing, but it doesn't look like it's actually dispatching actions when the fetched data is retrieved.
I'd recommend looking into using RTK's createAsyncThunk API to generate and dispatch actions as part of data fetching - see Redux Essentials, Part 5: Async Logic and Data Fetching for examples of that.
I am following a tutorial, where the teacher is using react-redux with classes. To understand it better, I decided to use hooks.
Unfortunatly I got stuck on a problem and am in need of your help.
The tutorial app uses oauth to signIn in gapi. For this a button is used, which, dependent on the authorization state show, that one can logIn or signOut. When signing in, one can choose in a popup window a google account.
But when I click on this button to logIn, I get an error, that says:
Error: Actions must be plain objects. Use custom middleware for async actions.
1) But in my app I use the hook called useEffect() with .then to omit the use of middleware like thunk. Or am I wrong on that?
2) Should I be using useEffect() and useDispatch() in my app or can be everything done with useSelector() (or/and useDispatch(), or/and useEffect())
3) The tutorial code uses { connect } which is an equivalent of useSelector(). Am I not using unnecessary useDispatch() and/or useEffect()? If yes, than how could I change the text of the button without dispatch?
Here is the tutorial code:
import React from 'react';
import { connect } from 'react-redux';
import { signIn, signOut } from '../actions';
class GoogleAuth extends React.Component {
componentDidMount() {
window.gapi.load('client:auth2', () => {
window.gapi.client
.init({
clientId:
'797401886567-9cumct9mrt3v2va409rasa7fa6fq02hh.apps.googleusercontent.com',
scope: 'email'
})
.then(() => {
this.auth = window.gapi.auth2.getAuthInstance();
this.onAuthChange(this.auth.isSignedIn.get());
this.auth.isSignedIn.listen(this.onAuthChange);
});
});
}
onAuthChange = isSignedIn => {
if (isSignedIn) {
this.props.signIn(this.auth.currentUser.get().getId());
} else {
this.props.signOut();
}
};
onSignInClick = () => {
this.auth.signIn();
};
onSignOutClick = () => {
this.auth.signOut();
};
renderAuthButton() {
if (this.props.isSignedIn === null) {
return null;
} else if (this.props.isSignedIn) {
return (
<button onClick={this.onSignOutClick} className="ui red google button">
<i className="google icon" />
Sign Out
</button>
);
} else {
return (
<button onClick={this.onSignInClick} className="ui red google button">
<i className="google icon" />
Sign In with Google
</button>
);
}
}
render() {
return <div>{this.renderAuthButton()}</div>;
}
}
const mapStateToProps = state => {
return { isSignedIn: state.auth.isSignedIn };
};
export default connect(
mapStateToProps,
{ signIn, signOut }
)(GoogleAuth);
And my code:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { signIn, signOut } from "../actions";
const API_KEY = process.env.REACT_APP_API_KEY;
const GoogleAuth = () => {
const isSignedIn = useSelector((state) => state.auth.isSignedIn);
console.log("IsSignedIn useSelector: " + isSignedIn);
const dispatch = useDispatch();
useEffect(
() => {
const onAuthChange = () => {
if (isSignedIn) {
dispatch(signIn(window.gapi.auth2.getAuthInstance().currentUser.get().getId()));
} else {
dispatch(signOut());
}
};
window.gapi.load("client:auth2", () => {
window.gapi.client
.init({
clientId: API_KEY,
scope: "email"
})
.then(() => {
onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get());
console.log("isSignedIn.get(): " + window.gapi.auth2.getAuthInstance().isSignedIn.get());
window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange);
});
});
},
[ dispatch, isSignedIn ]
);
const onSignInOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signIn());
};
const onSignOutOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signOut());
};
const renderAuthButton = () => {
if (isSignedIn === null) {
return null;
} else if (isSignedIn) {
return (
<button onClick={onSignOutOnClick} className="ui red google button">
<i className="google icon" />
Sign Out
</button>
);
} else {
return (
<button onClick={onSignInOnClick} className="ui red google button">
<i className="google icon" />
Sign In with Google
</button>
);
}
};
return <div>{renderAuthButton()}</div>;
};
export default GoogleAuth;
You have an error message:
Error: Actions must be plain objects. Use custom middleware for async actions.
That's right. Without middleware that can handle another type of action, that won't work.
1) But in my app I use the hook called useEffect() with .then to omit the use of middleware like thunk. Or am I wrong on that?
useEffect and .then aren't related to redux. You are wrong here. The actions you are using are the thunks (functions that return function(dispatch, getState)) and you definitely need a redux-thunk
2) Should I be using useEffect() and useDispatch() in my app or can be everything done with useSelector() (or/and useDispatch(), or/and useEffect())
You need useEffect to make a call to 3rd party API when component mounts.
You need useDispatch to get the dispatch function.
You need useSelector to get the value fro the redux-store.
3) The tutorial code uses { connect } which is an equivalent of useSelector(). Am I not using unnecessary useDispatch() and/or useEffect()? If yes, than how could I change the text of the button without dispatch?
The code: (can be broken)
const GoogleAuth = () => {
const isSignedIn = useSelector((state) => state.auth.isSignedIn);
const dispatch = useDispatch();
useEffect(
() => {
const onAuthChange = (isSignedInLocal) => {
if (isSignedInLocal) {
dispatch(signIn(window.gapi.auth2.getAuthInstance().currentUser.get().getId()));
} else {
dispatch(signOut());
}
};
window.gapi.load("client:auth2", () => {
window.gapi.client
.init({
clientId: API_KEY,
scope: "email"
})
.then(() => {
onAuthChange(window.gapi.auth2.getAuthInstance().isSignedIn.get());
window.gapi.auth2.getAuthInstance().isSignedIn.listen(onAuthChange);
});
});
},
[dispatch]
);
const onSignInOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signIn());
};
const onSignOutOnClick = () => {
dispatch(window.gapi.auth2.getAuthInstance().signOut());
};
const renderAuthButton = () => {
if (isSignedIn === null) {
return null;
} else if (isSignedIn) {
return (
<button onClick={onSignOutOnClick} className="ui red google button">
<i className="google icon" />
Sign Out
</button>
);
} else {
return (
<button onClick={onSignInOnClick} className="ui red google button">
<i className="google icon" />
Sign In with Google
</button>
);
}
};
return <div>{renderAuthButton()}</div>;
};
export default GoogleAuth;
Make sure you don't provide isSignedIn as a dependency for useEffect.
Redux has some specific use-cases and cons, but introduces some downsides like lots of boilerplate. Not knowing why you need redux is probably a sign you don't need redux.
However:
useState and useEffect are the two basic React primitives.
[state, setState] = useState(initialState) lets you use a state and set it;
useEffect(callback, [deps]) fires a callback whenever one of the dependencies change.
You probably don't need anything else.
Redux uses a different approach:
It uses a single, centralized state for the whole app (that you can get in any component with the useSelector() hook
It lets you change it via plain javascript objects (like { type: 'SIGN_IN', payload: { token: '42' } }), and define, for each of these objects, how the state should change (defining a so-called "reducer" function). You dispatch these state changes via the dispatch() function.
How about effects then?
Since dispatch only takes an object as argument (since the business logic is defined elsewhere), there's no point in passing to it a function or a promise.
But you can trigger them in several ways:
Directly in a callback, e.g. <button onClick={() => auth.signIn()}> Sign in </button>. That is absolutely fine.
Inside the useEffect hooks, if you need to trigger the effect as a reacion of a state change. E.g.: useEffect( () => console.log(`count changed, new value is: ${count}`), [count])
Via redux middleware
Option 1 and 2 are perfectly fine, but the downside is that you have to define business logic locally, instead of decoupling it from the view.
Defining it in a different place lets you scale and mantain the app more easily.
Custom middleware enhances the way dispatch() works.
One of the simplest way is redux-thunk.
It lets you pass a callback to dispatch:
// somewhere in the codebase:
function signIn(){
window.gapi.auth2.getAuthInstance().signOut()
}
...
// Inside you button:
<button onClick={() => dispatch(signIn())}> Sign Out </button>
There are more sofisticated effects model, but you can stick with redux-thunk unless you need something more powerful.
Just remember that each of these frameworks is just a tool with specific tradeoffs, and unless you really have a reason of using them, you can just stick with the excellent React primitives, instead of overengineering your app with no reason.
I try to implement a async react-select (Select.Async). The problem is, we want to do the fetch in redux-saga. So if a user types something, the fetch-action should be triggered. Saga then fetches the record and saved them to the store. This works so far.
Unfortunately loadOptions has to return a promise or the callback should be called. Since the newly retrieved options get propagated with a changing property, I see no way to use Select.Async together with saga to do the async fetch call. Any suggestions?
<Select.Async
multi={false}
value={this.props.value}
onChange={this.onChange}
loadOptions={(searchTerm) => this.props.options.load(searchTerm)}
/>
I had a hack where i assigned the callback to a class variable and resolve it on componentWillReceiveProps. That way ugly and did not work properly so i look for a better solution.
Thanks
redux-saga is for handling side effects like asynchronously receiving options for react-select. That's why you should leave the async stuff to redux-saga. I have never used react-select but by just looking at the documentation I would solve it this way:
Your component gets very simple. Just get value and options from your redux store. optionsRequested is an action creator for the OPTIONS_REQUESTED action:
const ConnectedSelect = ({ value, options, optionsRequested }) => (
<Select
value={value}
options={options}
onInputChange={optionsRequested}
/>
)
export default connect(store => ({
value: selectors.getValue(store),
options: selectors.getOptions(store),
}), {
optionsRequested: actions.optionsRequested,
})(ConnectedSelect)
A saga definition watches for OPTIONS_REQUESTED action that is trigged by onInputChange, loads the data with given searchTerm from server and dispatches OPTIONS_RECEIVED action to update redux store.
function* watchLoadOptions(searchTerm) {
const options = yield call(api.getOptions, searchTerm)
yield put(optionsReceived(options))
}
In other words: Make your Component as pure as possible and handle all side-effect/async calls in redux-saga
I hope this answer was useful for you.
The main idea is that you are capable to dispatch redux actions using application context from
import React from 'react';
import { connect } from 'react-redux';
import Select from '#components/Control/Form/Skin/Default/Select';
import { reduxGetter, reduxSetter, required as req } from '#helpers/form';
import { companyGetTrucksInit } from "#reduxActions/company";
import AppContext from '#app/AppContext';
const FIELD_NAME = 'truck';
export const getReduxValue = reduxGetter(FIELD_NAME);
export const setReduxValue = reduxSetter(FIELD_NAME);
const SelectCompanyTruck = (props) => {
const {
required,
validate=[]
} = props;
const vRules = [...validate];
if (required)
vRules.push(req);
return (
<AppContext.Consumer>
{({ dispatchAction }) => (
<Select
loadOptions={(inputValue, callback) => {
function handleResponse(response) {
const { data: { items } } = response;
const options = items.map(i => ({ label: i.name, value: i.id }));
callback(options);
}
dispatchAction(companyGetTrucksInit, { resolve: handleResponse, inputValue });
}}
name={FIELD_NAME}
{...props}
/>
)}
</AppContext.Consumer>
);
}
export default SelectCompanyTruck;
I have a few actions in my app that need to be shared by a few different react components. For example, I have a fetchAPIData action creator which accepts some params and fires off actions to to make a fetch (using a custom fetch middleware i've written):
export function fetchAPIData(params) {
const actions =
[
types.API_CALL,
types.RECEIVE_API_DATA,
types.API_ERROR
];
const params = {
method: 'params.method',
params
};
return fetch(actions, params);
};
This action and others like this needs to be called by various different parts of the app so i've created a common-actions directory where these actions live. This feels like the wrong approach so I am wondering if there is a more accepted way of doing this?
I would recommend you to use connect function react-redux
You can then import your actions to the top level component, and bind them with the state you want. then pass the props to any children you want. quick example:
import React, { Component } from 'react';
// Import actions below
import * as myActions from '/redux/actions/myActions';
// Make sure redux, react-redux are installed
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
class App extends Component {
render() {
const { redux, actions } = this.props; // the redux state.
const { actionName } = actions;
<div>
<button onClick={() => actionName()}> {/* Dispatching redux action... */}
Dispatch action
</button>
<ChildComponent {...this.props} />, {/* Now you can pass the props to any children */}
</div>;
}
}
// take any state from redux you want and return it within redux object
const mapStateToProps = state => ({
redux: {
myState: state.myState,
},
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators({
...myActions,
// import actions below...
}, dispatch),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App); // The component name you want to bind the state and the actions to.
I left notes so you can understand what's going on. This is ES6.