Dispatch action on route change with BrowserRouter - reactjs

I need to dispatch a Redux action every time there is a route change in my app. Unfortunately, these answers don't apply to my case, since I have to use BrowserRouter instead Router, and BrowserRouter does not take a history prop. Is there another way to do this? I am using V4.

Dennis' comment gave a working solution, using React hooks. Make a new component as follows:
const RouteChange = withRouter(({ location }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch({ type: "AN_ACTION" });
}, [dispatch, location.pathname]);
return null;
});
and just include it in the root of your application. The specified action will be dispatched whenever the route changes.

You can use componentwillunmount in each of your react class and it will work fine

Related

How to redirect from redux (w/ react router v6)?

With react-router 5 I used library - react-connected-router to make redirect from redux store. This library doesnt work with react router 6. How I can implement this?
I can just put navigate from useNavigate to payload and then in store use it. But I think it's not good decision. What can you recommend?
Can't you just navigate after you have dispatched your action to redux? Then you keep your navigation strictly to ReactComponents
wrap it in a function
let history = useHistory();
const myActions = () => {
dispatchAction1()
dispatchAction2()
history.push("/MY_ROUTE")
}

Redux Sagas with React Router v6 useNavigate Hook

Upgrading to React Router v6 and removed connected-react-router so now I'm needing a different way to navigate to a URL once a saga completes. We used to dispatch using connected-react-router with history.push() but we now need a way to use the useNavigate() hook instead.
I can set up my sagas to return a Promise and use a .then() within my components in some places, but in others that won't work.
We have sagas checking for multiple statuses and the navigate only needs to trigger on one or two of the status values. It seems like there should be a better way to use the useNavigate() hook without having to pass it in with each action that's dispatched to update the status.
Is there some way to allow useNavigate() to be available globally? Here's an example where I can't use the useNavigate() hook and don't necessarily want to pass it in as a function either.
// Saga
function* watchStatus(
action: ActionType<typeof startActions.changeStatus>
): SagaIterator {
if (action.payload.status === "END_CREATE") {
const data: ReturnType<typeof getDataRequest> = yield select(
getDataRequest
);
} else if (action.payload.status === "END_VIEW") {
// HERE IS WHERE I WOULD NEED TO NAVIGATE TO A NEW ROUTE
}
}
// State transition logic
export const statusChanges: {
[S in Status]: {
readonly onNext: (state: RootState) => StartStatus;
};
} = {
FEE_ESTIMATE: {
onNext: () => "END_CREATE"
},
END_VIEW_REPORT: {
onNext: () => "END_VIEW"
}
};
Ideally I would probably want to separate out concerns, actions, and routes to avoid this problem, but a large chunk of the app I'm working on has already been written "toggling" on these statuses within the same route so I'm trying to figure out a good workaround.
I wound up using redux-first-history as a replacement for connected-react-router where it didn't make sense to switch to using the useNavigate hook.

How can I run code on a route change from certain pages using react-router 5

I've found lots of examples on v4 with the onLeave function. it seems this was discontinued after v4 however.
I've seen some <Prompt> examples but dont think thats what i'm looking for. if you have a case that will work i am open to it.
My Scenario:
React v16 app where i have multiple tables. I have a section in the store (global state) where i retain certain ui preferences. the tables paginate and reuse some of the same state info for pagination, sort, etc.
user story - user selects page 4, then navigates to another table, is still on page 4 (pagination is read from the store). I simple want to purge the values from state (im using redux so i will call an action to do this) but how can i trigger that, for example, on only a few routes in my app. this way i can reset it on leave and its ready by the time the user gets to the next table?
//edit for comment 1, basic example
const routes = [
{
path: "/one-thing",
component: OneThing,
},
{
path: "/two-thing",
component: TwoThing,
},
...
this is a huge app, but this might clarify. How can i run code, when i leave the path /one-thing?
we used to be able to do this in v4
{
path: "/two-thing",
component: TwoThing,
onLeave: doAThing,
},
Sounds like you could either listen for changes to the current location via the history object in a parent component.
history.listen
import { useHistory } from 'react-router-dom';
import { useEffect } from "react";
...
const history = useHistory();
useEffect(() => {
const unlisten = history.listen((location, action) => {
console.log('Route changed', { location, action });
// Apply route change logic, i.e. dispatch to store
});
return unlisten;
}, []);
Or use unmounting useEffect cleanup function in the component with table.
import { useEffect } from "react";
...
useEffect(() => {
return() => {
// component unmounting (maybe from route change)
// Apply route change logic, i.e. dispatch to store
};
}, []);

How to detect URL changes in UmiJS React Web application?

There is BasicLayout.jsx in my application which pass down the props for main components and render the child components inside the layout. I want to perform some logic depends on URL changes inside the useEffect() of BasicLayout.jsx. Is there any efficient way to detect any URL changes with useLocation hook?
In this case that I want to re-render the BasicLayout.tsx, I used useHistoy() hook instead of useLocation() and passed the current URL pathname as dependency to the useEffect().
Here is the code snippet:
import { useHistory } from 'react-router-dom';
let url = useHistory();
useEffect(() => {
...
// perform some logic
...
}, [currentUser, declaration, url.location.pathname]);

Re-render component when navigating the stack with React Navigation

I am currently using react-navigation to do stack- and tab- navigation.
Is it possible to re-render a component every time the user navigates to specific screens? I want to make sure to rerun the componentDidMount() every time a specific screen is reached, so I get the latest data from the server by calling the appropriate action creator.
What strategies should I be looking at? I am pretty sure this is a common design pattern but I failed to see documented examples.
If you are using React Navigation 5.X, just do the following:
import { useIsFocused } from '#react-navigation/native'
export default function App(){
const isFocused = useIsFocused()
useEffect(() => {
if(isFocused){
//Update the state you want to be updated
}
}, [isFocused])
}
The useIsFocused hook checks whether a screen is currently focused or not. It returns a boolean value that is true when the screen is focused and false when it is not.
React Navigation lifecycle events quoted from react-navigation
React Navigation emits events to screen components that subscribe to them. There are four different events that you can subscribe to: willFocus, willBlur, didFocus and didBlur. Read more about them in the API reference.
Let's check this out,
With navigation listeners you can add an eventlistener to you page and call a function each time your page will be focused.
const didBlurSubscription = this.props.navigation.addListener(
'willBlur',
payload => {
console.debug('didBlur', payload);
}
);
// Remove the listener when you are done
didBlurSubscription.remove();
Replace the payload function and change it with your "refresh" function.
Hope this will help.
You can also use also useFocusEffect hook, then it will re render every time you navigate to the screen where you use that hook.
useFocusEffect(()=> {
your code
})
At the request of Dimitri in his comment, I will show you how you can force a re-rendering of the component, because the post leaves us with this ambiguity.
If you are looking for how to force a re-rendering on your component, just update some state (any of them), this will force a re-rendering on the component. I advise you to create a controller state, that is, when you want to force the rendering, just update that state with a random value different from the previous one.
Add a useEffect hook with the match params that you want to react to. Make sure to use the parameters that control your component so it rerenders. Example:
export default function Project(props) {
const [id, setId] = useState(props?.match?.params?.id);
const [project, setProject] = useState(props?.match?.params?.project);
useEffect(() => {
if (props.match) {
setId(props.match?.params.id);
setProject(props.match?.params.project);
}
}, [props.match?.params]);
......
To trigger a render when navigating to a screen.
import { useCallback } from "react";
import { useFocusEffect } from "#react-navigation/native";
// Quick little re-render hook
function useForceRender() {
const [value, setValue] = useState(0);
return [() => setValue(value + 1)];
}
export default function Screen3({ navigation }) {
const [forceRender] = useForceRender();
// Trigger re-render hook when screen is focused
// ref: https://reactnavigation.org/docs/use-focus-effect
useFocusEffect(useCallback(() => {
console.log("NAVIGATED TO SCREEN3")
forceRender();
}, []));
}
Note:
"#react-navigation/native": "6.0.13",
"#react-navigation/native-stack": "6.9.0",

Resources