Redirecting after successful action - reactjs

I am currently working on a authentication project within React Native using the react-navigation package. So far so good, I have 3 Navigators setup, one for loading, one for auth and one for the application.
I have also got the navigation setup with redux so I can access it from any component via a prop, which is exactly what I wanted to do.
However, I have a question. I have done some research and can't seem to figure out the best way to do this.
I am dispatching an action from a press of a button attemptLogin() the attempt login then does what it says on the tin, it attempt to login.
After a successful login, I store the access_token in SecureStore using expo.
Now, upon the successful login I need to navigate away from the current stack onto the new one.
What would be the best way to do this, can you return a promise from a redux action? If so would the best way to be inside of the component and then inside of the component do something like
this.props.login(username, password).then(() => { this.props.navigation... });
Or would you do it inside of the action?

A recommended way as below:
componentDidUpdate(prevProps) {
if(this.props.loggedInSuccessfully && !prevProps.loggedInSuccessfully) {
this.props.navigation.navigate....
}
}
onLoginButtonPress = () => {
const { username, password } = this.state;
this.props.login(username, password);
}
Upon successful logged in, update a state loggedInSuccessfully in your reducer and implement logic in componentDidUpdate. This way is the clearest for whoever gonna maintaining your code, in my humble opinion

you can also navigate from redux action or everywhere you want.
read the official docs about navigation service.

Related

How to properly save sensitive data on frontend using next.js?

I'm building a web app that has role permissions based, admin, user and also products, product A, B, C, etc. I get these data from a backend api.
Currently, I'm using local storage and useContext hook to save and manipulate these data, but an user that knows it, can easily change this information and manipulate the front end, so I'm wondering now which approach I can use here.
My wish (if it's possible) is to get these information by backend after the login, and reuse the data freely in other components, just importing it, like an useContext hook.
I know that there is Redux, but since I'm using next.js, from what I saw, every rendering it will lose/refresh data, so it won't be usefull here.
I'm also using SWR, so, I tried to get these data from cache.get('key'), but the SWR call must be on the same component to get the data properly from the key cached. It's not working if a call the SWR on the e.g home page, and try to get it in other generic component.
What do you people suggest to go here?
Thanks!
I think you should authenticate your user, then store their access key and identifier in localStorage and global state.
When users access an authorization required page.
You'll check for the access token if it doesn't exist on both global state and localStorage. Redirect (or alert) the authorization error.
If there is an access token. Then send a request to the server with that access token. The server will be in charge of authorizing progress. And you will handle the logic on the front end based on the response from the server.
The thing is the authorization (checking permission for instance) should be done in the backend, not on the frontend
I don't know whether you can manipulate the implementation of the backend API or not. Hope it helps
Following the answers, I created a useContext to use in any component that I need.
Here is what I have in my context:
const [userRoles, setUserRoles] = useState<string[] | undefined>([])
const getUsersRoles = useCallback(async () => {
const getUserRoles = await UsersService.getUsersRoles(userInfo.idUsuario)
setUserRoles(getUserRoles.data)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Note: UsersService.getUsersRoles function is integration with the API
And how I use it in the component I need:
const { userRoles, getUsersRoles } = useAuth()
if (userRoles?.length === 0) {
getUsersRoles()
return <LoadingGIf tip="Carregando opções..." />
}
With this, I have the data I need here userRoles. And IF the user reload/refresh the page, getUsersRoles is requested and set the data to the userRoles
The downside to this, at least for me, is that I have to add this:
const { userRoles, getUsersRoles } = useAuth()
if (userRoles?.length === 0) {
getUsersRoles()
return <LoadingGIf tip="Carregando opções..." />
}
for every component I need to use the roles data, but I believe that's it. It's working fine and isn't request extra any endpoints of API.
If anyone has a contribuitions or improves to do in the code, fell free to do it.

Better way to update react component on event

I'm making a web-app in which I use React, together with Firebase Authentication. I have a login page and I want it to redirect to a different page when a user is already signed in.
To check if the user is logged in I use a method to retrieve the User object, it returns null when a user is not signed in and a user object with all sorts of data if they are.
I have an if statement in my render method (of the login page) which checks if the user is logged in, but I ran into an issue. When a user first loads up the page it takes around half a second for the Firebase API to retrieve the user object and until that is completed my user object will return null.
This makes for an issue where it seems as though my user isn't logged in even if they are. That causes them not to redirect and stay on the login page until the state is updated in some way after the user object is initialized.
Firebase offers a way to fix this by giving us an onAuthStateChanged() method which allows me to execute a function when a user signs in or logs out.
What I'm doing now is using this method in the constructor method of my Login page class to manually re-render the component, thus redirecting the user when Firebase logs them in. It looks something like this:
export default class Login extends React.Component<Props, State> {
constructor(props:Props) {
super(props)
this.props.firebase.auth().onAuthStateChanged((user) => {
const oldState = this.state
this.setState(oldState)
})
}
render () {
if (this.props.firebase.auth().currentUser) {
return (
<Redirect to="/earn" />
)
} else {
return (
// Login page
)
}
}
}
(I omitted some irrelevant code)
Now this works fine and all but I'm not sure if this is the correct way to do it and I feel like I could make it look a lot nicer, I just don't know how.
Any suggestions or official ways to achieve what I'm doing right now are very much appreciated.

redux - Best way to handle action calls out of component while on 'dirty' state?

I'm using redux-logic to handle the business logic of my react-redux app.
I have component A with some fields. outside of this component, I have other components, that a user can click on them, and call actions.
while do some things in component A, the user can click on those other components.
What is the best way to 'catch' those calls, but first show some popup that says 'hey, are you sure?' and if the user click 'ok' - fire those calls?
so far i just managed to do something like this:
user call an action from component B for example (while still have unfinished business in component A)
the the logic that listen to this call, check if we are in dirty mode. If it is, we reject the call.
two things that not feels right for me :
I need to implement this checking in EVERY call
it does not tells me to show the pop up.
If you were using redux-thunk, then something like this would be as easy as:
function goingAwayAction(){
return (dispatch, getState) => {
const state = getState();
if (Selectors.formIsDirty(state)) {
confirm("Are you sure?", () => { dispatch(GoAway()) })
} else {
dispatch(GoAway())
}
}
}
I don't know redux-logic in particular, but your general solution is checking for form dirtiness as part of your async action. There are good libraries for confirmations which let you just specify a callback which happens when they say yes, that'll simplify some logic for you. I've used react-confirm-alert before and had success with it!
Sounds like you need to use a middleware. Redux-saga should be the answer.
Checkout: https://redux-saga.js.org/docs/basics/DispatchingActions.html

Using this.props.history.push("/path") is re-rendering and then returning

Edited the question after further debugging
I am having a strange issue, tried for a while to figure it out but I can't.
I have a React Component called NewGoal.jsx, after a user submits their new goal I attempt to reroute them to my "goals" page.
The problem: After they submit the browser loads in my goal page, but only for one second. It then continues and goes BACK to the NewGoal page!!
I am trying to understand why this is happening, I am beginning to feel that this might be an async issue.
Here is my code, currently it is using async-await, I also tried the same idea using a .then() but it also didn't work:
async handleSubmit(event)
{
const response = await axios.post("http://localhost:8080/addGoal",
{
goalID: null,
duration: this.state.days,
accomplishedDays: 0,
isPublic: this.state.isPublic,
description: this.state.name,
dateCreated: new Date().toISOString().substring(0,10),
}) */
// push to route
this.props.history.push("/goals");
}
While debugging, I tried taking out the functionality where I post the new message, and just did a history.push, code is below - and this completely worked.
// THIS WORKS
async handleSubmit(event)
{
// push to route
this.props.history.push("/goals");
}
But as soon as I add anything else to the function, whether before the history.push or after, it stops working.
Any advice would be very very appreciated!
Thank you
In the React Router doc's the developers talk about how the history object is mutable. Their recommendation is not to alter it directly.
https://reacttraining.com/react-router/web/api/history#history-history-is-mutable
Fortunately there are few ways to programmatically change the User's location while still working within the lifecycle events of React.
The easiest I've found is also the newest. React Router uses the React Context API to make the history object used by the router available to it's descendents. This will save you passing the history object down your component tree through props.
The only thing you need to do is make sure your AddNewGoalPage uses the history object from context instead of props.
handleSubmit(event)
...
//successful, redirect to all goals
if(res.data)
{
this.context.history.push("/goals")
}
...
})
}
I don't know if you're using a class component or a functional component for the AddNewGoalPage - but your handleSubmit method hints that it's a member of a Class, so the router's history object will be automatically available to you within your class through this.context.history.
If you are using a functional component, you'll need to make sure that the handleSubmit method is properly bound to the functional component otherwise the context the functional component parameter is given by React won't not be available to it.
Feel free to reply to me if this is the case.

Accessing react-router from flummox action/store

I want to be able to make an API call in a Flummox action and transition differently depending on the response. I can pass the router into the action call but am looking for advice on a potentially better way.
UPDATE:
The correct answer is below but I wanted to add some detail to this.
I'm doing an isomorphic app that 1. needs to get data from an api and 2. may need to redirect based on the api response. Whatever I do needs to work through an express.js app and through react.
I made a small lib that does the api call and return some results. I pass it an object (query params object from express for the server-side or a similar object I create for the react-side). This lib makes the request, determines if a redirect is needed and passes back errors, path (string), redirect (boolean), and data (json).
In express, if the redirect boolean is true, I just redirect to it with the current query params. If it's false, I pass the data to flux through an action which updates a store. I then renderToString with react, serialize stores so the clint-side can bootstrap, and send a rendered page to the client.
In react, the redirect boolean isn't important, I get the response back from my lib, pass the data to my flux action, and just transition to whatever the path is. There's really no notion of redirection. Just go to the path no matter what.
Hopefully this is helpful to someone.
In my setup I have my own router module which just wraps the instance of react-router that I create at startup. That makes it easy for any part of the application to just require that module and do what it needs to.
But I would advise you not to have side effects like a call to the router inside your actions. Actions should concern themselves on mutating your application state, and nothing more. It should be possible to call the same action from anywhere in your application which needs to perform the mutation that the action encapsulates.
So if you're switching routes during an action, you're basically tying that action to that particular use case. Let's take an example. You have a todo list, with an input box at the bottom to add a new todo. For that use case, it might be useful to switch route after you saved the todo. Perhaps you switch to Recent Todos or something. But then a new use case comes along where you want to be able to add new todos during another workflow, perhaps the user is planning her week and should be able to add todos on different days. You want the same action that adds a todo, but you certainly don't want to switch routes because the user is still planning the week.
I haven't used Flummox a lot, but from my understanding your Flux object returns whatever the action returns when you trigger an action. So instead of putting the route change inside your action, make sure to return the response from the action and let your component decide if the route should be changed. Something like this:
// todo-action.js
class TodoActions extends Actions {
createMessage(todo) {
return TodoStore.saveToServer(todo);
}
}
// todo-list.js
const TodoList extends React.Component {
render() {
...
}
addTodo(todo) {
this.props.flux.addTodo(todo).then(response => {
if (response.some.prop === someValue) {
this.props.router.transitionTo(...);
}
});
}
}
That way, the action is still nicely decoupled from the route change. If you want to do the route switch in more than one place, you could encapsulate that in a addTodoAndSwitchRoute method in your Flux class.

Resources