I am trying using react-router and redux to build my react app. However, I am unable to get the url to route back to the dashboard post login. Can somebody please point out my mistakes?
const form = reduxForm({
form: 'login'
});
class Login extends Component {
handleFormSubmit(formProps) {
this.props.loginUser(formProps);
var token = cookie.load('token');
if(token !== undefined){
const location = this.props.location;
if (location.state && location.state.nextPathname) {
browserHistory.push(location.state.nextPathname)
} else {
browserHistory.push('/')
}
}
}
renderAlert() {
if(this.props.errorMessage) {
return (
<div>
<span><strong>Error!</strong> Authentication error</span>
</div>
);
}
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
{this.renderAlert()}
<div>
<label>Username</label>
<Field name="username" className="form-control" component="input" type="text" />
</div>
<div>
<label>Password</label>
<Field name="password" className="form-control" component="input" type="password" />
</div>
<button type="submit" className="btn btn-primary submitButton">Login</button>
</form>
</div>
);
}
}
function mapStateToProps(state) {
return {
errorMessage: state.auth.error,
message: state.auth.message
};
}
const mapDispatchToProps = dispatch => ({
loginUser: () =>
dispatch(loginUser);
});
export default connect(mapStateToProps, mapDispatchToProps)(form(Login));
My loginUser function is as below:
export function loginUser({ username, password }) {
return function(dispatch){
axios.post(`${AUTH_URL}/obtain-auth-token/`, { username, password}, {
headers: {
"X-CSRFToken": cookie.load('csrftoken')
}
})
.then(response => {
if('token' in response.data){
cookie.save('token', response.data.token, {path: '/'});
dispatch({type: AUTH_USER});
} else{
console.log("Error condiction: " + response);
errorHandler(dispatch, error.response, AUTH_ERROR);
}
})
.catch((error) => {
errorHandler(dispatch, error.response, AUTH_ERROR);
});
}
}
This is my first react-redux project so the mistake might be pretty elementary. Would really appreciate your help!
The root of your issue appears to be with handling your async call - in your handleFormSubmit(formProps) function you have the following two lines:
this.props.loginUser(formProps);
var token = cookie.load('token');
You are dispatching your action that will be running your async function (loginUser(formProps) does a post using axios), and then you immediately try to consume the results of this async function by loading the token that it should have stored in a cookie upon success. This does not work because immediately upon running an async function, JavaScript will not wait for the results but instead will return to your handleFormSubmit function and run the rest of it through completion. I am sure that if you console.log'd your token it will be undefined (assuming there were no cookies before running the app) - the function continued without waiting for your async function.
So I know two good options that you can use to fix this:
Simple, standard solution: You run dispatch({type: AUTH_USER}); upon the success of your async post - have this action creator result in a change in your state held by redux (ex: loginFlag). Include loginFlag as a prop in your Login component (include it in your mapStateToProps function). Finally, include a componentWillReceiveProps(nextProps) lifecycle function to your Login component and have it handle the route change. Include something like:
componetWillReceiveProps(nextProps) {
if (nextProps.loginFlag) {
var token = cookie.load('token');
if(token !== undefined){
const location = this.props.location;
if (location.state && location.state.nextPathname) {
browserHistory.push(location.state.nextPathname)
} else {
browserHistory.push('/')
}
}
}
}
Or:
Heavier solution using another package: You can use the npm package react-router-redux (https://github.com/reactjs/react-router-redux), to do a push to browserHistory within your redux action at the end of your async call - doing so will require you to setup its middleware and hook it up to your redux store. More info: https://github.com/reactjs/react-router-redux#api
I would definitely recommend looking into react-router-redux. If you do not want the browserHistory functions available in your redux actions, then it is very simple to setup in your project (don't need to make changes to the store or add its middleware) and it allows you to track your url path within your redux state which can be very helpful.
Related
I'm creating a React web app which would show a modal on every POST request asking users to add a approval token. This approval token would be added to the request header, before sending the request to the server.
I'm using axios to make a request as follows:
axios({
url: '/backend/call',
method: 'POST'
})
I'm intercepting the request through axios interceptors, to show the modal here. The modal has a text box and a submit button.
axios.interceptors.request.use(function (config) {
if (config.method === 'POST') {
ApprovalPopup.useApprovalPopup.showPopup(); // This displays the modal which has text field and Submit button
const token = --------; // How do I get the token from the Modal here, after submit is clicked.
config.headers.approvalToken = token;
}
return config;
});
Modal code for reference:
export type IApprovalPopup = {
showPopup: () => void;
};
const ApprovalPopupContext = createContext<IApprovalPopup>({
showPopup: () => undefined,
});
export default function ApprovalPopupProvider({children}: { children: ReactNode }) {
const dialogRef = useRef<HTMLDialogElement>(null);
const [ticketID, setTickedID] = useState('');
function handleSubmit() {
// update state and make backend call
}
function showPopup() {
dialogRef.current?.show()
}
return (
<ApprovalPopupContext.Provider
value={{
showPopup
}}
>
<span slot="body">
<div className="ticket-entry">
<Label> Ticket ID</Label>
<TextField onTextFieldChange={(event) => handleTicketIDEntry(event.detail.value)}
label="Ticket-ID"
value={ticketID}>
</TextField>
</div>
<div className="submit-section">
<div className="button">
<Button className="button" onClick={handleSubmit}>
Submit
</Button>
</div>
</div>
</span>
{children}
</ApprovalPopupContext.Provider>
);
}
ApprovalPopupProvider.useApprovalPopup = () => useContext(ApprovalPopupContext);
How do I get the token from the Modal and append it in the header before making the call to the server?
I could technically let the first call fail, and pass the config to the Modal as well. The Modal would then attach the header to the config and make the required call. But in that case how would I make sure the response goes back to the actual calling component. The calling component is updating local state objects / handling custom errors etc, so it needs the promise to be resolved
I think for you case it would be better to set axios header in handleSubmit function
simply add this :
function handleSubmit() {
axios.defaults.headers.common['approvalToken'] = token;
// update state and make backend call
}
I was going to build autocomplete using remix.run, but then it occurred to me that would be relying too much on routing and forms to select/focus the input after each submit, this can not produce good UX. The user will input something into form, the form gets submitted, he awaits response, and then the input is focused again, and the ux here that this is instantsearch/autocomplete.
What you need is the useFetcher hook Remix exports. This hook let you fetch data from the loader of any route without causing a navigation, it was added for this kind of UIs.
import { Form, useFetcher } from "remix"
export default function Screen() {
let fetcher = useFetcher()
function handleChange(event) {
let value = event.currentTarget.value
// load data from a route with a loader
fetcher.load(`/api/autocomplete?query=${value}`)
}
return (
<Form>
<input type="text" onChange={handleChange} list="suggestions" />
<datalist id="suggestions">
{fetcher.data.map(item => {
return <option key={item.id} value={item.value} />
})}
</datalist>
</Form>
)
}
Something like that, and in the endpoint you load with the fetcher you export a loader
export async function loader({ request }) {
let url = new URL(request.url)
let query = url.searchParams.get("query") ?? "";
let data = await getData(query)
return json(data)
}
I have a very strange issue. I have a login page in React where users can either Sign-Up or Login .
When the user signs up, I create a user in GUNjs. And when I login, it logins successfully. However, when I open the site in incognito mode and try to login with the same creds, it says that the user doesn't exist. I also have a peer server running. The peer server saves user's data, but still this happens.
My code:
decentralized_db.ts
import GUN from "gun";
import "gun/sea";
import "gun/axe";
export const db = GUN({
peers: ["http://localhost:8765/gun"],
});
export const user = db.user().recall({ sessionStorage: true });
peerServer.js
gun = (Gun = require("gun"))({
web: require("http").createServer(Gun.serve(__dirname)).listen(8765),
});
My React component:
loginPage.tsx:
import { useState } from "react";
import { user } from "../decentralized_db";
export default function LoginPage() {
const [userCred, setUserCred] = useState({
username: "",
password: "",
});
function login(event: any) {
event.preventDefault();
user.auth(
userCred.username,
userCred.password,
({ err }: any) => err && alert(err)
);
console.log(user);
}
function signup(event: any) {
event.preventDefault();
user.create(userCred.username, userCred.password, ({ err }: any) => {
if (err) {
alert(err);
} else {
login(event);
}
});
}
function handleChange(event: any) {
let name = event.target.name;
let value = event.target.value;
let clonedState: any = { ...userCred };
clonedState[name] = value;
setUserCred(clonedState);
}
return (
<form>
<label htmlFor="username">Username</label>
<input
onChange={handleChange}
name="username"
value={userCred.username}
minLength={3}
maxLength={16}
/>
<label htmlFor="password">Password</label>
<input
onChange={handleChange}
name="password"
value={userCred.password}
type="password"
/>
<button className="login" onClick={login}>
Login
</button>
<button className="login" onClick={signup}>
Sign Up
</button>
</form>
);
}
#pranava-mohan hey this is a bug, there was a race condition in the profile syncing - the login would check credentials before the profile finished loading.
To confirm this is the same bug as you are experiencing: Try to click login multiple times on the other page. Does it login on later attempts, even if you changed nothing? Then this was the bug.
Working to have this fixed in future versions. Until then, the best place to get help with bugs is http://chat.gun.eco we reply faster there.
Sorry about that! Please comment if it winds up being something else.
I have an AWS Amplify app using React. I want to be able to only load (or reload) a TaskList component only when the user has successfully signed in. However, the component gets rendered from the very beginning when page loads and when user fills up form and gets signed up it won't reload. I have been trying multiple workarounds but I can't see how to make my component depend on a successful login. I rely on the default Amplify authenticator functions to sign the user in against Cognito.
const App = () => (
<AmplifyAuthenticator>
<div>
My App
<AmplifySignOut />
<TaskList />
</div>
</AmplifyAuthenticator>
);
I managed to solve it using hints given in this answer AWS Amplify: onStatusChange then render main page.
Basically, I changed my App component to return only sign in form or the whole up based on auth state change.
const App = () => {
const [authState, setAuthState] = useState('');
function handleAuthStateChange(state) {
if (state === 'signedin' || state === 'signedout') {
setAuthState(state);
}
}
return (
<div>
{ authState !== 'signedin' ?
<AmplifyAuthenticator>
<AmplifySignIn handleAuthStateChange={handleAuthStateChange} slot="sign-in"></AmplifySignIn>
</AmplifyAuthenticator>
:
<div>
My App
<AmplifySignOut handleAuthStateChange={handleAuthStateChange} slot="sign-out"/>
<TaskList />
</div>
}
</div>
);
}
This is how I solved a similar issue to manage the states. I was having some problems as it didn't seem to dispatch the events afterwards.
From https://github.com/aws-amplify/amplify-js/issues/5825
import React from 'react';
import { AmplifyAuthenticator, AmplifySignOut, AmplifySignUp, AmplifySignIn} from '#aws-amplify/ui-react';
import { onAuthUIStateChange } from '#aws-amplify/ui-components'
const Panel = () => {
const [setAuthState] = React.useState();
React.useEffect(() => {
return onAuthUIStateChange(newAuthState => {
if(newAuthState === 'signedin'){
// Do your stuff
}
setAuthState(newAuthState)
});
}, []);
return(
<AmplifyAuthenticator>
<AmplifySignIn headerText="Sign In" slot="sign-in"/>
<AmplifySignUp slot="sign-up" formFields={[
{type: 'username'},
{type: 'email'},
{type: 'password'}
]}></AmplifySignUp>
<AmplifySignOut></AmplifySignOut>
</AmplifyAuthenticator>
)
}
export default Panel;
I am trying to submit an address form using redux form. It seems like a good way of handling the input data and validation.
I am just wondering if I can make the syntax a bit cleaner, because, frankly, trying to use connect at the same time makes the code a mess at the bottom. In my case, I want to send the address data to a Node endpoint, so I need to call an action generator which sends an AJAX request. I'm wondering if I'm missing something obvious to make dispatching an action inside the submit function easier.
class AddressForm extends Component {
renderContent() {
return formFields.map(({ name, label }) => (
<Field
key={name}
name={name}
label={label}
type='text'
component={FormField}
/>
)
);
};
render() {
return (
<form onSubmit={this.props.handleSubmit}>
{this.renderContent()}
<button type="submit">Next</button>
</form>
);
};
};
const validate = (values) => {
const errors = {};
errors.email = validateEmail(values.email || '');
formFields.forEach(({ name }) => {
if (!values[name]) errors[name] = 'Please provide a value';
});
return errors;
};
const myReduxForm = reduxForm({
validate,
form: 'addressForm'
})(AddressForm);
const mapDispatchToProps = (dispatch, ownProps) => ({
onSubmit: data => dispatch(submitForm(data, ownProps.history))
});
export default connect(null, mapDispatchToProps)
(withRouter(myReduxForm));
Sure, instead of connecting, you can use the handleSubmit prop inside your component. It allows you to supply a callback with three arguments: values, dispatch and props. So you can do something like:
<form onSubmit={this.handleSubmit((values,dispatch,{submitForm})=> dispatch(submitForm(values)))} />