Search code examples
reactjsreact-reduxreact-router-domredux-thunk

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


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.


Solution

  • 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 (<></>)
    }