`push` from "connected-react-router" used in redux-observable epic doesn't change the url and renders empty page - react-router-v4

push from "connected-react-router" used in redux-observable epic doesn't change the url and renders empty page. state.router.location never changes, so I think that the action does not get dispatched properly, but the components are not rendered any more - that's a change I can't figure out.
The app is as follows:
In reducers:
const rootReducer: Reducer<any, any> = history => combineReducers({
router: connectRouter(history),
})
In app config:
const history = createBrowserHistory({
basename: ROOT_PATH,
})
<Provider store={store}>
<ConnectedRouter history={history}>
<RootContainer />
</ConnectedRouter>
</Provider>
const configureStore = (): Store => {
return createStore(
rootReducer(history),
applyMiddleware(createEpicMiddleware(rootEpic)),
)
}
In RootContainer.js
import { withRouter } from "react-router-dom"
const Root = withRouter(connect(mapStateToProps, mapDispatchToProps)(RootComponent))
export default Root
In epics:
import { push } from "connected-react-router"
const navigateTo = (action$: ActionsObservable<Action>): ActionsObservable<Action> => (
action$.pipe(
ofType(SharedActions.OPEN_WINDOW),
mergeMap((action) => {
return of(push(action.payload.url))
}),
)
)
package.json
"connected-react-router": "^5.0.1",
"react": "^16.2.0",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"redux": "^3.6.0",
"redux-observable": "^0.17.0",
"rxjs": "^5.5.7",
I don't have any hot module replacement like in this example, but I think it's not related.
UPDATE:
I've added epics to listen for:
import { CALL_HISTORY_METHOD, LOCATION_CHANGE, push } from "connected-react-router"
It seems that the following action gets dispatched:
{
type: "##router/CALL_HISTORY_METHOD"
payload: {
args: [ "new/path" ]
method: "push"
}
}
​
​It just doesn't have any effect on the url.
UPDATE
Also using Link (react-router-dom) to directly navigate to the "new/path" works great inside components, so the path is correct.

It looks like you may have forgotten to add the routerMiddleware when creating your Redux store:
https://github.com/supasate/connected-react-router#step-2
Your app config above should be:
import { routerMiddleware } from "connected-react-router";
const history = createBrowserHistory({
basename: ROOT_PATH,
})
<Provider store={store}>
<ConnectedRouter history={history}>
<RootContainer />
</ConnectedRouter>
</Provider>
const configureStore = (): Store => {
return createStore(
rootReducer(history),
applyMiddleware(
createEpicMiddleware(rootEpic),
routerMiddleware(history),
),
)
}

Related

Why is my navigation ref not ready in React Navigation 6 with Redux?

In React Navigation 6, my research shows that to navigate without a prop I should make a reference and use createNavigationContainerRef. I'm able to pass down the screen name to my dispatch but for some reason when I evaluate the condition with isReady I'm always told it isn't. The code:
App.js:
import React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import 'react-native-gesture-handler'
// Provider
import { Provider as AuthProvider } from './src/context/AuthContext'
// Navigation
import { navigationRef } from './src/navigation/NavRef'
// Screens
import ResolveAuthScreen from './src/screens/ResolveAuthScreen'
const App = () => {
return (
<AuthProvider>
<NavigationContainer ref={navigationRef}>
<ResolveAuthScreen />
</NavigationContainer>
</AuthProvider>
)
}
export default App
ResolveAuthScreen.js:
import React, { useEffect, useContext } from 'react'
// Context
import { Context as AuthContext } from '../context/AuthContext'
const ResolveAuthScreen = () => {
const { tryLocalSignIn } = useContext(AuthContext)
useEffect(() => {
tryLocalSignIn()
}, [])
return null
}
export default ResolveAuthScreen
AuthContext.js (stripped down):
import AsyncStorage from '#react-native-async-storage/async-storage'
// Context
import createContext from './createContext'
// Nav
import * as NavRef from '../navigation/NavRef'
const authReducer = (state, action) => {
switch (action.type) {
case 'signin':
return { errorMessage: '', token: action.payload }
case 'clear_error':
return { ...state, errorMessage: '' }
default:
return state
}
}
const tryLocalSignIn = dispatch => async () => {
const token = await AsyncStorage.getItem('token')
console.log({ token }) // renders token
if (token) {
dispatch({ type: 'signin', payload: token })
NavRef.navigate('TrackListScreen')
} else {
NavRef.navigate('SignUp')
}
}
export const { Provider, Context } = createContext(
authReducer,
{ tryLocalSignIn },
{ token: null, errorMessage: '' },
)
NavRef.js:
import { createNavigationContainerRef } from '#react-navigation/native'
export const navigationRef = createNavigationContainerRef()
export function navigate(name, params) {
console.log({ name, params })
if (navigationRef.isReady()) {
console.log('ready')
console.log({ name, params })
navigationRef.navigate('TrackDetailScreen', { name, params })
} else {
console.log('not ready')
}
}
When I log the token from dispatch I get back the token. When I log the screen I get back TrackListScreen from navigate but whenever it's fired it always returns the console log of not ready.
Docs:
Navigating without the navigation prop
Navigating to a screen in a nested navigator
"dependencies": {
"#react-native-async-storage/async-storage": "~1.15.0",
"#react-navigation/bottom-tabs": "^6.0.9",
"#react-navigation/native": "^6.0.6",
"#react-navigation/native-stack": "^6.2.5",
"axios": "^0.24.0",
"expo": "~43.0.0",
"expo-status-bar": "~1.1.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.2",
"react-native-elements": "^3.4.2",
"react-native-gesture-handler": "~1.10.2",
"react-native-reanimated": "~2.2.0",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.8.0",
"react-native-web": "0.17.1"
},
Why is my navigate not working after my dispatch or why does the isReady false?
I'm having the same issue. When trying to access the exported navigationRef.isReady() from a redux-saga file, it always returns false. I'm not sure this is a safe approach, nor have I properly tested this, but the following workaround seems to work for me:
App.js
import {setNavigationRef, navigationIsReady} from './NavigationService';
const navigationRef = useNavigationContainerRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
setNavigationRef(navigationRef);
}}>
...
</NavigationContainer>
);
NavigationService.js
export let navigationRefCopy = undefined;
export function setNavigationRef(navigationRef) {
navigationRefCopy = navigationRef;
}
export function navigationIsReady() {
return navigationRefCopy?.isReady(); // returns true when called in a redux saga file.
}

React Native - I18Next with react-navigation and typescript

I have this RN project I started. I need localization in it, and I tried numerous solution for it. I18Next looks like it could really well suit my needs. I'm not sure how to define it in its own file and call it in App.tsx. I used a useEffect but I doubt it's wise - even more without any dependencies. Here is what I tried :
i18n.ts:
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as Localization from 'expo-localization';
import en from './en.json';
import fr from './fr.json';
const resources = {en: { translation: en }, fr: { translation: fr }};
i18next
// .use(languageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: true,
resources,
lng: Localization.locale.slice(0, 2),
});
export default i18next;
And the code in App.tsx:
// ...
import i18next from './src/locales/i18n';
export default function App(): React.ReactElement {
useEffect(() => {
i18next
.init()
.then(() => {
// TODO - dispatch to redux when this is ready
})
.catch(error => console.warn(error));
});
return (
<Provider store={store}>
<PersistGate loading={<SplashScreen />} persistor={persistor}>
<StatusBar hidden />
<MainNavigation />
</PersistGate>
</Provider>
);
}
I get a warning i18next: init: i18next is already initialized. You should call init just once!. Indeed, I'm calling .init() twice - in the main file and again in the useEffect. But I don't see how to do it otherwise.
Also, is my useEffect alright?
[EDIT] I found https://react.i18next.com/latest/i18nextprovider in the doc and using it and deleting the useEffect, the warning is gone - although I'm not sure if it's a good idea since the docs states You will only need to use the provider in scenarios for SSR (ServerSideRendering) or if you need to support multiple i18next instances - eg. if you provide a component library.
Actually I just needed to init it once indeed, and didn't need to use the provider either. Finally nailed it like this:
const onBeforeLift = async () => {
await initI18Next();
};
export default function App(): React.ReactElement {
return (
<Provider store={store}>
<PersistGate
loading={<SplashScreen />}
persistor={persistor}
onBeforeLift={onBeforeLift}
>
<StatusBar hidden />
<MainNavigation />
</PersistGate>
</Provider>
);
}
And in i18n.ts:
const resources = {
en: { translation: en },
fr: { translation: fr },
he: { translation: he },
};
export const init = () => {
const lng = store.getState().user.language
? store.getState().user.language
: Localization.locale.slice(0, 2);
return (
i18next
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: true,
resources,
lng,
})
);
};
export default i18next;

How to stop Fast refresh reset route to initialRoute automatically in React Native

How are you.
I am using React Native 0.62.2.
The problem is when code updated, it goes to initalRoute automatically, so I need to navigate to screen I was working to check updated.
How can I stop it so when updated code, fast refresh shows update on current screen?
"react": "16.11.0",
"react-native": "0.62.2",
"#react-navigation/bottom-tabs": "^5.2.7",
"#react-navigation/drawer": "^5.5.0",
"#react-navigation/material-bottom-tabs": "^5.1.9",
"#react-navigation/native": "^5.1.5",
"#react-navigation/stack": "^5.2.10",
Thanks
For those who are running into this problem with React Native Web.
I fixed this by configuring Linking with React Navigation.
const config = {
screens: {
Login: 'login',
Home: '',
About: 'about',
},
};
const linking = {
prefixes: ['https://example.com',],
config,
};
<NavigationContainer linking={linking}>
...
</NavigationContainer>
Once Linking was set up, fast refresh no longer reseted the navigation to the first route.
You can write a component that record the state of the navigation and stores it on asyncStorage. Maybe something like so:
import { InitialState, NavigationContainer } from "#react-navigation/native";
import React, { useCallback, useEffect, useState } from "react";
import { AsyncStorage } from "react-native";
const NAVIGATION_STATE_KEY = `NAVIGATION_STATE_KEY-${sdkVersion}`;
function NavigationHandler() {
const [isNavigationReady, setIsNavigationReady] = useState(!__DEV__);
const [initialState, setInitialState] = useState<InitialState | undefined>();
useEffect(() => {
const restoreState = async () => {
try {
const savedStateString = await AsyncStorage.getItem(
NAVIGATION_STATE_KEY
);
const state = savedStateString
? JSON.parse(savedStateString)
: undefined;
setInitialState(state);
} finally {
setIsNavigationReady(true);
}
};
if (!isNavigationReady) {
restoreState();
}
}, [isNavigationReady]);
const onStateChange = useCallback(
(state) =>
AsyncStorage.setItem(NAVIGATION_STATE_KEY, JSON.stringify(state)),
[]
);
if (!isNavigationReady) {
return <AppLoading />;
}
return (
<NavigationContainer {...{ onStateChange, initialState }}>
{children}
</NavigationContainer>
);
}
I'd check LoadAsset Typescript component from #wcandillon here
Maybe you can try this way.
I follow the State persistence document of React Navigation to write the code down below.
https://reactnavigation.org/docs/state-persistence/
import * as React from 'react';
import { Linking, Platform } from 'react-native';
import AsyncStorage from '#react-native-community/async-storage';
import { NavigationContainer } from '#react-navigation/native';
const PERSISTENCE_KEY = 'NAVIGATION_STATE';
export default function App() {
const [isReady, setIsReady] = React.useState(__DEV__ ? false : true);
const [initialState, setInitialState] = React.useState();
React.useEffect(() => {
const restoreState = async () => {
try {
const initialUrl = await Linking.getInitialURL();
if (Platform.OS !== 'web' && initialUrl == null) {
// Only restore state if there's no deep link and we're not on web
const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
const state = savedStateString ? JSON.parse(savedStateString) : undefined;
if (state !== undefined) {
setInitialState(state);
}
}
} finally {
setIsReady(true);
}
};
if (!isReady) {
restoreState();
}
}, [isReady]);
if (!isReady) {
return <ActivityIndicator />;
}
return (
<NavigationContainer
initialState={initialState}
onStateChange={(state) =>
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))
}
>
{/* ... */}
</NavigationContainer>
);
}

Cannot read property 'name' of null (react-admin)

I have the most simple code possible to check react-admin:
import React, { Component } from "react";
import buildGraphQLProvider from "ra-data-graphql-simple";
import { Admin, Resource } from "react-admin";
import posts from "./routes/posts";
class App extends Component {
constructor() {
super();
this.state = { dataProvider: null };
}
componentDidMount() {
buildGraphQLProvider({
clientOptions: { uri: "https://countries.trevorblades.com" }
})
.then(dataProvider => this.setState({ dataProvider }))
.catch(e => console.log(e.message));
}
render() {
const { dataProvider } = this.state;
if (!dataProvider) {
return <div>Loading</div>;
}
return (
<Admin dataProvider={dataProvider}>
<Resource name="posts" {...posts} />
</Admin>
);
//or, directly return <Admin dataProvider={dataProvider} />
}
}
export default App;
but I always get the same error in console: Cannot read property 'name' of null
My dependences are:
"graphql": "14.6.0",
"graphql-tag": "2.10.1",
"ra-core": "3.1.4",
"ra-data-graphql-simple": "3.1.4",
"react": "16.12.0",
"react-admin": "3.2.0",
"react-apollo": "3.1.3",
"react-dom": "16.12.0",
"react-scripts": "3.0.1"
What I'm doing wrong??
I had the same problem.
The issue was that I was missing Mutation type in my graphql schema, therefore the check
type.name !== schema.mutationType.name
threw an error, because schema.mutationType was undefined
Make sure you have a Mutation type in your schema even if it's an empty one
Maybe you wanted to write?
<Resource name="posts" />
Have you read the docs?
https://marmelab.com/react-admin/Resource.html

ReactNavigation Error - Cannot read property 'bind' of undefined

"react-native": "^0.57.0",
"react-navigation": "^3.0.0",
"react-navigation-redux-helpers": "^2.0.7",
"react-redux": "^5.0.6",
"redux": "^4.0.0",
"redux-thunk": "^2.3.0",
"reduxsauce": "0.7.0",
"react-native-gesture-handler": "^1.0.9",
Updating react-navigation to 3.0.0 in my React-Native app. I have followed the official docs here React Navigation and installed all the dependencies.
However cannot resolve this issue.
AppNavigation.js
const PrimaryNav = createStackNavigator({
HomeScreen: {
screen: MainTabNav,
}, {
mode: 'modal',
headerMode: 'none',
initialRouteName: 'HomeScreen',
navigationOptions: {
headerStyle: styles.header,
},
});
export default createAppContainer(PrimaryNav);
ReduxNavigation.js
import AppNavigation from './AppNavigation';
import { reduxifyNavigator, createReactNavigationReduxMiddleware } from
'react-navigation-redux-helpers';
createReactNavigationReduxMiddleware(
'root',
state => state.nav,
);
const ReduxAppNavigator = reduxifyNavigator(AppNavigation, 'root');
render() {
const { dispatch, nav } = this.props;
<ReduxAppNavigator state={nav} dispatch={dispatch} />
}
const mapStateToProps = state => ({ nav: state.nav });
export default connect(mapStateToProps)(ReduxNavigation);
navigation.js
import { Keyboard } from 'react-native';
import AppNavigation from '../navigation/AppNavigation';
export default (state, action) => {
Keyboard.dismiss();
const newState = AppNavigation.router.getStateForAction(action, state);
return newState || state;
};
CreateStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import reduxThunkMiddleware from 'redux-thunk';
import { createReactNavigationReduxMiddleware } from 'react-navigation-redux-
helpers';
import screenTrackingMiddleware from './screenTrackingMiddleware';
export default (rootReducer) => {
const middleware = [];
const enhancers = [];
const navigationMiddleware = createReactNavigationReduxMiddleware(
'root',
state => state.nav,
);
middleware.push(screenTrackingMiddleware);
middleware.push(navigationMiddleware);
middleware.push(reduxThunkMiddleware);
enhancers.push(applyMiddleware(...middleware));
const store = createStore(rootReducer, compose(...enhancers));
return {
store,
};
};
RootContainer.js
import ReduxNavigation from 'navigation/ReduxNavigation';
export default class RootContainer extends Component {
render() {
return (
<View style={styles.applicationView}>
<StatusBar barStyle="light-content" />
<ReduxNavigation />
</View>
);
}
}
This happens because you didn't link react-native-gesture-handler
Just type react-native link in your project dir.
And run again react-native run-android
Solved the issue by adding this plugin to .babelrc file.
[
"#babel/plugin-transform-flow-strip-types",
]