SvelteKit form actions and redirect - sveltekit

In my SvelteKit app I have a form that uses a form action:
<form action="/login?/logout" method="POST">
<button type="submit">Logout</button>
</form>
In this action the final step is to redirect:
export const actions: Actions = {
logout: async (event) => {
// ...
throw redirect(307, '/login');
}
}
This was working as I intended. After updating to SvelteKit 1.0.0 the behavior changed: The action is called (as before) but when redirecting I get the following error:
Error: No action with name 'default' found
Any ideas what needs to be changed or how this should be done?

Status code 307 will not change the method and body of the request, so the browser will try to POST /login.
You could change the status code to 303 instead to change the method to GET:
export const actions: Actions = {
logout: async (event) => {
// ...
throw redirect(303, '/login');
}
}

Related

Combine SvelteKit's throw Redirect with Felte's onSuccess

The problem:
Nothing happends when throwing throw redirect(302, '/auth/sign-up-success') in SvelteKit's actions if onSuccess: () => {...} is set in Felte's createForm({...}).
Example:
// +page.server.ts
export const actions: Actions = {
default: async (event) => {
...
throw redirect(302, '/auth/sign-up-success');
}
}
// SignUpForm.svelte
const { form, errors } = createForm({
onSuccess: (response) => {
invalidate('app:auth')
},
...
}
If I would delete the onSuccess part, then redirect would happend.
Question:
Is there a way to reuse that redirect form success response logic from default Felte form config without writing it again myself?
Action responses are JSON objects with a type, you could read the response and redirect on the client:
async onSuccess(response) {
const { type, location } = await response.json();
if (type == 'redirect') {
goto(location); // from '$app/navigation'
return;
}
}
I would not recommend using this library though. It appears to be incompatible with SSR and one of its main actions shares the name of the form data property used by SvelteKit form actions.
Depending on why you are using this, there might be more suitable tools for SvelteKit in particular (if you even need any, SvelteKit does many things out of the box).

Redux toolkit RTK query mutation not getting returning data

Hi I recently learned the new react toolkit with the rtk query tool, and I am trying to put in a login system together using the createApi from the rtk package.
After giving it a test on the login button pressed, I see the network request going through without any issue(status code 200), and I get a response object providing user, token, however, when I try to get the returning data using useLoginMutation I get an undefined value.
below is the code for my endpoint which is injected in a base api:
export const apiLogin = theiaBaseApi.injectEndpoints({
endpoints: (build) => ({
loginUser: build.mutation<UserReadonly, loginValuesType | string>({
query: (values: loginValuesType, redirect?: string) => {
const { username, password } = values;
const header = gettingSomeHeaderHere
return {
url: "login",
method: "GET",
headers,
crossDomain: true,
responseType: "json",
};
},
}),
}),
});
export const { useLoginUserMutation } = apiLogin
then inside my React component I destructure the mutation result such like below:
const [login, {data, isLoading}] = useLoginUserMutation();
const submitLogin = () => {
// pass in username password from the form
login({username, password});
}
Suppose if I console log out data and isLoading I assume that I will see data: {user: "abc", token: "xyz"}, because under network tab of my inspect window I can see the response of this network request, but instead I am seeing data: undefined
Does any have experience on solving this?
Oh I found the reason, it was a very careless mistake. I had to wrap the reducer to my store, which was what I was missing
In my case the issue was that I was trying to access the UseMutationResult object inside onClick callback. And the object was not updating inside the callback, even though in the component the values were accurate.
If I put the log outside it's working just fine.
here is an example for better understanding (inside handleAddPost the mutationResult is not updating)
Here is a code sample (in case link is not working):
const Component = () => {
const [addPost, mutationResult] = useAddPostMutation();
...
const handleAddPost = async () => {
...
console.log("INSIDE CALLBACK isLoading and other data is not updating:");
console.log(JSON.parse(JSON.stringify(mutationResult)))
...
};
// in the example this is wrapped in an useEffect to limit the number of logs
console.log(mutationResult.data,"OUTSIDE CALLBACK isLoading and other data is working:")
console.log(JSON.parse(JSON.stringify(mutationResult)))
return (
...
<Button
...
onClick={handleAddPost}
>
Add Post
</Button>
...

How to catch changes in redux store after dispatching async action to redux-saga

In my Login component i try to login on button click, corresponding function 'handleLogin' is then getting called. In this function i dispatch an async function with my user credentials as a payload. In my saga, i am making a request putting an error to store if the response got such field, else i set a user in there. By default the error field in store is 'false'. In my component where i dispatch an action, i want to know the state of error field in store right after the successfull/unsuccessful response. When i try to login in with wrong credentials and log the state of error into the console, i first get the 'old' (initial) value of error: false, instead of error: true. Only after second login try, the error get set to true. Is there a way to know the actual state of error in store, right after dispatching async action?
Reducer:
const initialState = {
userData: [],
error: false
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case SET_USER:
return {
...state,
userData: action.payload
}
case SET_ERROR:
return {
...state,
error: action.payload
}
default:
return state;
}
};
SAGAS:
function* handleUserLoad(payload) {
try {
const user = yield call(
loginUser,
payload
);
if(user.data.errors) {
yield put(setError(true))
} else {
yield put(setError(false))
yield put(setUser(user.data.data.loginUser));
}
} catch (error) {
console.log(error);
}
}
export default function* userSaga() {
while (1) {
const {payload} = yield take(GET_USER);
yield call(handleUserLoad,payload);
}
}
PART OF LOGIN COMPONENT:
const handleLogin = async() => {
setShouldValidate(true);
if (allFieldsSet) {
try {
loginUser(user)
// LOGIN ERROR HERE DISPLAYS THE PREVIOUS STATE 'false'
// INSTEAD OF ACTUAL TRUE AFTER loginUser
// IF I LOG WITH WRONG CREDENTIALS
console.log(loginError);
} catch (e) {
console.error(e);
}
}
};
loginUser(user) in your component is an async function, so you can't expect console.log(loginError); to log the value of loginError correctly in next line.
Solution:
First:
You can bind your login component to error state in redux. Once the state is modified by Saga based upon successful/unsuccesful response, your login component will re-render. So, in render if you will console.log then you may see the value of loginError coming correctly. So, in render only you can put the component what you want on the basis of "loginError" to be shown.
Second:
You can skip using Saga and directly await for your login API response(call API using fetch/axios from here itself) in Login Component at the current place itself. In that case you will be able to get the correct login response just after the await calls gets completed and than you can continue executing your code
I have made slight changes to default value of error in store. Now it is 'null' instead of false. And thus i can check if it was set to false/true in useEffect and do the other logic.

Unhandled promise issue when dispatching redux thunk action in React Native

Hi I am having real difficulty getting a redux thunk action to work as expected in React Native. I have used the exact same method with an application in React which works perfectly, however cannot for the life of me work out why it does not work in my React Native application, throwing an unhandled promise rejection warning, breaking my code.
My login function is as follows:
this.props.loginUser(loginData)
.catch(err => this.setState({
errors: err.response.data.errors
}))
And has been connected to redux as follows:
export default connect(null, {loginUser})(Login)
loginUser function is as follows:
export const userLoggedIn = (user) => ({
type: LOGGED_IN,
user
})
const loginUser = (credentials) => (dispatch) =>
api.user.login(credentials)
.then(user => dispatch(userLoggedIn(user)))
export default loginUser
And POST request to server:
export default {
user: {
login: (credentials) =>
axios.post('http://10.0.0.103:3000/auth', {credentials})
.then(user => user.data.credentials)
}
}
So everything works as expected up until I try to call dispatch with the action. I have included thunk middleware which I assumed would allow all this as per my working React application, however I am getting Unhandled Promise rejection: cannot read property 'data' of undefined in this case, which confuses me.
Thanks for any help in advance

Display loading state and change route when API call is successfull

While working on a side project, I faced an issue with react-router-dom.
What I want to implement is: When I submit a Form, I need to save the data on my server. While the request is pending, I need to display a loading indicator. Once the server says everything is ok, I need to redirect the user on a new page
action.js
export const addNotification = value => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
} catch(e) {
dispatch(addNotificationFailure())
}
}
component.js
class CreateNotificationForm extends Component {
onSubmit = (values) => {
this.props.addNotification(parameters, history)
}
render() {
const { isCreating } = this.props
const submitBtnText = isCreating ? 'Creating...' : 'Submit'
return (
<Form>
// content omitted
<Submit value={submitBtnText} />
</Form>
)
}
}
const mapStateToProps = (state) => ({
isCreating: getIsFetching(state)
})
const mapDispatchToProps = (dispatch) => ({ // omitted })
connect(mapStateToProps, mapDispatchToProps)(CreateNotificationForm)
So far so good: When I submit my form, the form's submit button shows a Creating... text.
However, how do I tell react-router to load a new path once the request is successful?
Right now, I've done that by using withRouter and using this.props.history as a second argument for this.props.addNotification.
It works great, but it seems really wrong
I've seen solutions using react-router-redux, but I don't really want to add a new middleware to my store.
Should I make the API call inside my component and use a Promise?
Any help?
Update:
After working a little on my own React project, and thinking about similar situations where I handle route changes there, I decided I want to change my original answer. I think the callback solution is OK, but the solution that you already mentioned of making the API call inside your component and using a promise is better. I realized that I've actually been doing this in my own app for a while now.
I use redux-form in my app, and it provides onSubmitSuccess/onSubmitFail functions that you can use to handle the submit result, and each of those rely on you returning a promise (usually from your action creator).
I think the fact that one of the most popular packages for form submission in React/Redux supports this pattern is an indication that it's probably a good pattern to use. Also, since react-router passes history into your component, it seems logical that they expect most people to do a lot of their programmatic route changes inside the component.
Here's an example of what the promise solution would look like with your code:
action.js
export const addNotification = value => dispatch => {
return new Promise(async (resolve, reject) => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
resolve(response)
} catch(e) {
dispatch(addNotificationFailure())
reject(e)
}
})
}
component.js
onSubmit = async () => {
try {
await this.props.addNotification(parameters)
this.props.history.push('/new/route')
} catch(e) {
// could use a try/catch block here to display
// an error to the user here if addNotification fails,
// or go to a different route
}
}
Old Answer:
A simple solution would be to allow addNotification() to accept a callback function as an optional second argument.
export const addNotification = (value, callback=null) => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
(typeof callback === 'function') && callback()
} catch(e) {
dispatch(addNotificationFailure())
}
}
Then inside your component use the router to go to the new route.
onSubmit = (values) => {
this.props.addNotification(parameters, () => {
this.props.history.push('/new/route')
})
}
You should not write your asynchronous calls in reducers or actions as the documentation clearly suggests them to be pure functions. You will have to introduce a redux-middleware like redux-thunk or redux-saga (I personally prefer sagas)
All your async calls will happen inside the middleware, and when it succeeds, you can use react-routers history .replace() or .push() methods to update your route. Let me know if it makes sense
You can use one popular package axios
See Here https://www.npmjs.com/package/axios
and you can implement your login like
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
You can write your loader login while calling api
and then you can hide your loader in .then

Resources