what is the best way to use react router V6 navigation with redux and redux thunk actions?
Asked Answered
N

3

5

I am making a react app (not react-native) using React-v17, and react-redux V7, and React Router V6, searching and reading many articles, I could not find a way to navigate programmatically inside redux actions using the V6 hooks, as hooks can only be called inside components, here is what I have

registerPage.jsx

import React, { Component } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { LockClosedIcon } from '@heroicons/react/solid'
import Loader from 'react-loader-spinner'

import { registerUser } from '../../../state_man/store/register'

const RegisterForm = (props) => {
  const [registerFields, setRegisterFields] = useState({})
  const [errors, setErrors] = useState({})
  const [validInput, setValidInput] = useState({})
  const [loading, setLoading] = useState(false)

  let navigate = useNavigate()
  let params = useParams()

  let auth = useSelector((state) => state.auth)
  let mainUi = useSelector((state) => state.UI.mainUI)
  let registerUi = useSelector((state) => state.UI.registerPage)

  const dispatch = useDispatch()

  const { isLoggedIn } = auth

const onRegisterUser = (e) => {
    e.preventDefault()

    const credentials = {
      username: registerFields['username'],
      ['phone']: registerFields['phone'],
      email: registerFields['email'],
      region: registerFields['region'],
      password: registerFields['password'],
      address: registerFields['address'],
      client_name: registerFields['client_name'],
    }
    dispatch(registerUser(credentials))
return (

    // my form is so long, i made it short just for demonstration purposes! 
    <form>
      <input type='text' />
      <input type='passowrd' />
      <button
        onClick={(e) => {
          // here i call the function that does the registration ,
 //that included calling a function that dispatches API action,
          onRegisterUser(e)
        }}
      ></button>
    </form>
  )

  }

in my state management module, I create a function that dispatch API actions, what

register.js

const url = '/client'
export const registerUser = (credentials) => {

  return apiActionCreators.apiCallBegan({
    url,
    method: 'post',
    data: credentials,
    onStart: START_LOADING.type,
    onEnd: END_LOADING.type,
    onSuccessFunc: (data) => (dispatch, store) => {
      dispatch(USER_REGISTERED(data))
      dispatch(CLEAR_MAIN_ERROR())
      // i want to take the user to a specific page using something like navigate("menu")
    },
    onErrorFunc: (error) => (dispatch, store) => {
      dispatch(ADD_MAIN_ERROR(error))
    },
  })
}

as I commented, I want to execute a function in that action inside "onSuccessFunc" so that it takes the user to a specific page , i know i can make a reducer and use it for navigation, i send action with a payload to where i want to navigate, and in a HOC, i check if there is a path to navigate to, if i true, i clear it and i navigate , some thing like this !

let navigation = useSelector((state) => state.navigation)

  useEffect(() => {
    if (navigation.path) {
      dispatch(
        navigationActions.CLEAR_NAVIGATION_PATH()
      )
      navigate('/navigation.path')
    }
  }, []) 

this does seem to be a good solution, and i think there can be a lot of DRY and shortcomings, what is the best way to do this! and do you see any shortcomings in my solution!, remember i am using react-router V6! thanks.

None answered 31/1, 2022 at 15:11 Comment(3)
Does https://mcmap.net/q/129246/-react-router-v6-navigate-outside-of-components or https://mcmap.net/q/374368/-react-router-v6-how-to-use-navigate-redirection-in-axios-interceptor answer your question? The first is the general solution for exposing out programmatic navigation outside the router and React app, the second is a more specific use case, you can swap the usage in axios interceptors for usage in your asynchronous action creators.Foofaraw
yes they do, yet i thought i would find a middleware or something provided by RRDv6 itself, and it seems what they have is "unstable", and I find the first answer to create my hooks also good, so thank you, I ll accept the first answer, and make a comment.None
connected-react-router is a solid solution if you decide to rollback to RRDv5, though hopefully there's an update to allow compatibility with RRDv6. It may be worth keeping an eye on in case they do.Foofaraw
F
4

You'd better do the navigation directly in your callback. You can take advantage of the rule of Hooks: Call Hooks from custom Hooks.

// register.js or useRegister.js

export const useRegisterUser = () => {

  const navigate = useNavigate();

  const doRegister = (credentials) => {
    return apiActionCreators.apiCallBegan({
    url,
    method: 'post',
    data: credentials,
    onStart: START_LOADING.type,
    onEnd: END_LOADING.type,
    onSuccessFunc: (data) => (dispatch, store) => {
      dispatch(USER_REGISTERED(data))
      dispatch(CLEAR_MAIN_ERROR())
      // allowed since it's inside a hook
      navigate("menu")
    },
    onErrorFunc: (error) => (dispatch, store) => {
      dispatch(ADD_MAIN_ERROR(error))
    },
  })
}

return {
    doRegister 
}

}
// registerPage.js

import { useRegisterUser } from './register.js'
// other imports 

const RegisterForm = (props) => {

  const { doRegister } = useRegisterUser()

// other stuffs
// grab credentials

    dispatch(doRegister(credentials))

// other stuffs

return (<></>)
}
Farfamed answered 31/1, 2022 at 15:41 Comment(0)
C
3

For me I was trying to wait for the result of a function created with createAsyncThunk. I knew to use dispatch, but was unsure of how to do anything on success or failure. I found my answer in the redux docs here. Turns out the returned action of dispatch can be passed into fulfilled, rejected, etc, properties on the async function that was created to check for its response state.

Relevant section:

const handleUpdateUser = async (userData) => {
  const resultAction = await dispatch(updateUser(userData))
  if (updateUser.fulfilled.match(resultAction)) {
    const user = resultAction.payload
    showToast('success', `Updated ${user.name}`)
  } else {
    if (resultAction.payload) {
      // Since we passed in `MyKnownError` to `rejectValue` in `updateUser`, the type information will be available here.
      // Note: this would also be a good place to do any handling that relies on the `rejectedWithValue` payload, such as setting field errors
      showToast('error', `Update failed: ${resultAction.payload.errorMessage}`)
    } else {
      showToast('error', `Update failed: ${resultAction.error.message}`)
    }
  }
}

and more specifically, using the updateUser.fulfilled.match(resultAction).

I used this to do a navigation after login. Here is what my login submit function looked like.

  const handleLoginSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    const email: string = formData.get('email') as string;
    const password: string = formData.get('password') as string;
    if (!email || !password) {
      return;
    } else {
      const resultAction = await dispatch(loginAsync({ email, password }))
      if (loginAsync.fulfilled.match(resultAction)) {
        navigate('/dashboard');
      } else if (loginAsync.rejected.match(resultAction)) {
        console.log('Failed!');
      }
    }
  }
Crenellate answered 22/8, 2022 at 18:43 Comment(0)
N
1

after searching and reading some comments, i found 3 possible solutions, 1st is the first answer yet I was not quite comfortable using it as I did not know the implementation details of hooks, I tried it and it worked well, 2nd option is to create your own CustomRouter, give it a history object that you create, and make changes to the history object you made as specified here: https://mcmap.net/q/374368/-react-router-v6-how-to-use-navigate-redirection-in-axios-interceptor, thanks to Drew for pointing it out. 3rd is to use an object that you could import from RRDv6, and is called unstable_HistoryRouter , and you can find it here also https://mcmap.net/q/374368/-react-router-v6-how-to-use-navigate-redirection-in-axios-interceptor.

None answered 31/1, 2022 at 22:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.