I integrated react-apollo for fetching data from my GraphQL server into my react native application. I use redux-perist for persisting and rehydrating the redux store.
But I don't know how to rehydrate my apollo state.
Anyone know how I can do that?
Thanks
You can use the autoRehydrate feature of redux-persist. It will asyncronously run an action with type REHYDRATE that will rehydrate all stores unless you blacklist them.
If you try to execute a query that was previously executed, Apollo will check your Redux store first and you should see an APOLLO_QUERY_RESULT_CLIENT action execute (meaning it's returning from the client store instead of querying the server). You can modify the fetchPolicy to specify how the query gets its data (network only, cache first, etc.)
Here's a basic setup:
import React, { Component } from 'react';
import { ApolloProvider } from 'react-apollo';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { persistStore, autoRehydrate } from 'redux-persist';
import { AsyncStorage } from 'react-native';
const networkInterface = createNetworkInterface({ uri: 'http://localhost:8080/graphql' });
export const client = new ApolloClient({
networkInterface: networkInterface,
});
const store = createStore(
combineReducers({
apollo: client.reducer(),
}),
{}, // initial state
compose(
applyMiddleware(client.middleware()),
autoRehydrate(),
),
);
// persistent storage
persistStore(store, {
storage: AsyncStorage, // or whatever storage you want
});
export default class App extends Component {
render() {
return (
<ApolloProvider store={store} client={client}>
<YourApp />
</ApolloProvider>
);
}
}
Related
I'm building a react-native app with Expo, when I doing refactor job and suddenly I can't open my app with Expo client, it shows an error
TypeError: (0, _redux.combinReducers) is not a function. (In '(0, _redux.combineReducers)(reducers)', '(0, _redux.combineReducers)' is undefined)
after some researching I see some reason cause this issue is cause the same folder name with redux, which I made this mistake also, so I change the folder name to "appRedux", after that the error message change to
Unable to resolve "../../../../src/redux" from "node_modules\react-redux\lib\connect\mapDispatchToProps.js"
I'm sure I've wrapped my App component in Provider as this solution said, but just no luck to solve it.
I've been stuck in here for few days, please help me to solve this issue, if need more info please let me know, any suggestion is appreciated.
App.js
import React, { Component } from 'react';
import Navigation from './src/navigation'
import { Provider } from 'react-redux'
import { getStore, getPersistor } from './src/store/configureStore'
import { PersistGate } from 'redux-persist/integration/react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { ApolloProvider } from '#apollo/react-hooks'
import { getClient } from './src/services/GraphQL/Client'
const store = getStore()
const persistor = getPersistor()
const client = getClient()
export default class App extends Component {
render() {
return (
<ApolloProvider client={client}>
<Provider store={store} >
<PersistGate persistor={persistor}>
<SafeAreaProvider>
<Navigation />
</SafeAreaProvider>
</PersistGate>
</Provider>
</ApolloProvider>
);
}
}
configureStore.js
import { createStore, applyMiddleware } from 'redux'
import { persistStore } from 'redux-persist'
import reducers from '#redux'
import thunk from 'redux-thunk'
const store = createStore(reducers, applyMiddleware(thunk))
const persistor = persistStore(store)
const getPersistor = () => persistor
const getStore = () => store
export {
getStore,
getPersistor
}
Reducers.js
import { combineReducers } from 'redux'
import { persistCombineReducers, persistReducer } from 'redux-persist'
import { AsyncStorage } from 'react-native'
import carts from './carts'
import app from './app'
import user from './user'
const rootConfig = {
key: 'root',
storage: AsyncStorage,
blacklist: [
'app',
'carts',
'user'
]
}
const appConfig = {
key: 'app',
storage: AsyncStorage,
blacklist: [
'selectedDate',
'selectedDateIndex',
'isFetching',
'showFilter'
]
}
const cartConfig = {
key: 'carts',
storage: AsyncStorage,
blacklist: [
'isFetching',
'error'
]
}
const userConfig = {
key: 'user',
storage: AsyncStorage,
blacklist: [
'isFetching',
'error',
'history'
]
}
export default persistCombineReducers(rootConfig, {
app: persistReducer(appConfig, app),
carts: persistReducer(cartConfig, carts),
user: persistReducer(userConfig, user)
})
Finally I solve this issue by start expo client with
expo start -c
which will start expo app with clearing the cache, hope this will help someone facing the same issue
I am using react-native with redux-persist and I cannot seem to get my store to persist into async storage. According to the redux-persist github repo, this may have to do with AsyncStorage being removed from react-native, but I cannot seem to figure out what needs to be done to fix the issue.
When my app launches I see the actions persist/PERSIST and persist/REHYDRATE fire in my redux devtools, but nothing ever is stored in async storage.
I thought I had set up my file as the redux-persist docs explained but I imagine I am missing something. I'm fairly new to Redux as it is so I may just be confused on something basic.
My Redux.config.js file ...
import React from 'react';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux'
import thunk from 'redux-thunk';
import { initialState } from './initialState';
import { puppyReducer } from './reducers/puppies/puppyReducer';
import { uiReducer } from './reducers/ui/uiReducer';
import { userReducer } from './reducers/user/userReducer';
import { contentReducer } from './reducers/content/contentReducer';
import { persistStore, persistReducer, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/lib/storage'
import logger from 'redux-logger';
import Navigation from './Navigation.config';
import { PersistGate } from 'redux-persist/integration/react';
const persistConfig = {
key: 'root',
storage: storage
}
const rootReducer = {
dogs: puppyReducer,
ui: uiReducer,
user: userReducer,
content: contentReducer
};
const reducer = persistCombineReducers(persistConfig, rootReducer);
const persistedReducer = persistReducer(persistConfig, reducer);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(persistedReducer, initialState, composeEnhancers(applyMiddleware(thunk), applyMiddleware(logger)));
const persistor = persistStore(store);
// Wrap the redux State Provider around the Main Navigation Stack
function StateWrapper() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Navigation />
</PersistGate>
</Provider >
)
}
export default App = StateWrapper;
I expect that this should load my persisted state when the app launches, and continue to update that persisted state as my redux state changes. As it stands currently, my state is updating fine, but nothing is persisted. There are no error messages showing.
I persist my redux store using AsyncStorage. Rather than using redux-persist's storage option, I have installed AsyncStorage from the #react-native-community. Just make sure that you link the dependency properly.
Then in my config.js file I import and use AsyncStorage as follows.
import AsyncStorage from '#react-native-community/async-storage';
const config = {
key: 'primary',
keyPrefix: '', // the redux-persist default `persist:` doesn't work with some file systems
storage: AsyncStorage,
};
Your issue could also be related to the keyPrefix try setting it to some value other than the default.
Try the following:
const reducer = persistCombineReducers(persistConfig, rootReducer);
const persistedReducer = persistReducer(persistConfig, reducer rootReducer);
This works for me:
//...
import {combineReducers} from 'redux';
import {persistReducer} from 'redux-persist';
const persistConfig = {
key: 'root',
storage: storage
}
const rootReducer = {
dogs: puppyReducer,
ui: uiReducer,
user: userReducer,
content: contentReducer
};
const persistedReducer = persistReducer(
persistConfig,
combineReducers(rootReducers)
);
//...
I have been using react-native-router-flux in my app and configured the store and it works like a charm.
I am now planning to use react-navigation v2.8 in my app and i am struggling to complete the configureStore.
I supposed that the configureStore would be used as is from the react-native-router-flux example. Below is the sample code from my working react-native-router-flux sample.
/* global __DEV__ */
import { Router } from 'react-native-router-flux'; //this is not to be used
in react-navigation
import { connect } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore } from 'redux-persist';
import ReduxPromise from 'redux-promise';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import reducers from './index';
connect()(Router); //ROuter is not avaible in react-navigation
let middleware;
if (__DEV__) {
const logger = createLogger();
middleware = [thunk, ReduxPromise, logger];
//middleware = [thunk, ReduxPromise];
} else {
middleware = [thunk, ReduxPromise];
}
export function configureStore() {
const store = compose(applyMiddleware(...middleware))(
createStore
)(reducers);
const persistor = persistStore(store);
return { persistor, store };
}
export const { persistor, store } = configureStore();
React Navigation can be used as a general JSX Component that can be enclosed within the Provider and thus the whole UI tree will have the store available inside the context. So you can just initialize the store in your app's root, keep it as a state variable inside
it and pass it to the context using Provider.
Code for configuring your store and making it available down in your UI tree made using React Navigation should look like below:
Your Root component should look like this
import React, {Component} from 'react';
import {Provider} from 'react-redux';
import AppStack from './app/AppNavigation';
import getStore from './app/store';
export default class App extends Component {
constructor (props) {
super(props);
this.state = {
store : getStore()
}
}
render() {
return (
<Provider store={this.state.store} >
<AppStack /> . // This is your React Navigation Stack.
</Provider>
);
}
}
and your getStore should look like this:
import { createStore } from 'redux';
import reducers from './reducers';
export default function getStore () {
const enhancer = compose(
applyMiddleware(
//your middlewares here..
)
);
const store = createStore(reducers);
return store;
}
and the AppStack can look like this:
import {createStackNavigator} from 'react-navigation';
import FirstScreen from './components/FirstScreen';
export default AppStack = createStackNavigator({
Screen1: {
screen: FirstScreen,
//other routes....
},
});
This is just a mere simplest possible example.
I have delete_task action that POST the task id to PHP file to delete the task , although the action I fired correctly and i see the effect in the console but the task not deleted, i tried many many times using GET OR POST OR JUST put the id statically in the URL but nothing changing,can anyone help me to solve the problem
tasksAction.js
export const delete_task = id => ({
type: DELETE_TASK,
payload: {id},
meta: {
offline: {
// the network action to execute:
effect: { url: 'http://localhost/todos/remove.php', method: 'POST',body: {id} },
// action to dispatch when effect succeeds:
commit: { type: DELETE_SUCCESS, meta: { id } },
// action to dispatch if network action fails permanently:
rollback: { type: DELETE_FAILED, meta: { id } }
}
}
});
store.js
import React, { Component } from 'react';
import { applyMiddleware, createStore, compose } from 'redux';
import { offline } from '#redux-offline/redux-offline';
import offlineConfig from '#redux-offline/redux-offline/lib/defaults';
import {Provider} from 'react-redux';
import logger from 'redux-logger';
import RootNavigator from "./config/routes";
import reducers from './reducers'
// store
const middleware = [];
if(process.env.NODE_ENV === 'development'){
middleware.push(logger);
}
const store = createStore(
reducers,
undefined,
compose(
applyMiddleware(...middleware),
offline(offlineConfig)
)
);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<RootNavigator />
</Provider>
)
}
}
In the offline object inside prev state, does it say online: false?
If it does, and you're developing in an Android emulator, Wifi is not available on the emulator if you are using below API level 25, so redux-offline thinks you have no connection and will not make the initial network request.
This occurred to me after testing redux-offline in an iOS simulator and everything worked.
Hope this helps.
I am having trouble initialising my redux-state when my react-native application boots up. I need to make an api call before the application boots up to retrieve data to hydrate my state. Id like to pass the result of this call to the createStore function in my Provider JSX element.
I have read different things about how to do that but none of them seems to work.
Here's my root App component :
import React, { Component } from 'react';
import { View } from 'react-native';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './reducers';
import RouterComponent from './Router';
class App extends Component {
render() {
return (
<Provider store={createStore(reducers, {}, applyMiddleware(ReduxThunk))}>
<View style={{ flex: 1 }}>
<RouterComponent />
</View>
</Provider>
);
}
}
export default App;
I have read and tried different strategies :
- wrapping the return statement of the render method in the then callback of the api call
- make the call in componentWillMount or componentDidMount
None of this did work for me. What is the standard way to pass createStore an initial state from an API call when react-native application boots up.
You cannot (and should not) delay the mounting of components until the API call returns (it might even fail).
You can show a loading screen while waiting for the API call to return, by checking if a certain Redux state (that will be populated by the API result) is still empty in your component (conditional rendering).
If you want to replace the entire Redux state with your API result, you need to write a root reducer, see the answer here.
To initiate the API call when app starts and populate the state when it succeeds, you can add the following to anywhere that the Redux store is defined/imported:
fetch(...).then(result => store.dispatch(...))
Instead of populating the Redux state from server, you can look into persisting it with the client if it suits your use case.
I would go about this by setting a loading value in the state and then requesting the data in ComponentDidMount(). Once loaded, set this.state.loaded to true and render the store with the data returned from the API. It isn't necessary to do this but it would provide a good UX for the client and prevent unnecessarily re-rendering the RouterComponent twice.
Whether or not you decide to set error and loaded values, the idea here is to get the data in the ComponentDidMount method and update App.state with the new data - this will cause the component to re-render and apply your data to the new Store.
import React, { Component } from 'react';
import { View } from 'react-native';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './reducers';
import RouterComponent from './Router';
class App extends Component {
constructor(props) {
super(props);
this.state = {
initialState: {},
loaded: false,
error: false
}
}
componentDidMount() {
// Perform your API call, using which ever library or method you choose, i prefer axios so will demonstrate with this:
axios.get('path/to/api')
.then(res => {
// Send the response to state, which will cause the component to re-render and create the store with the new initialState
this.setState({
initialState: res.data,
loaded: true
});
})
.catch(err => {
console.error('Error initiating application. Failed to retrieve data from API')
this.setState({error: true});
});
}
render() {
// This would be completely optional, but I would show some form of loading icon or text whilst you wait for the API to fetch the data.
if(!this.state.loaded) {
return "Loading";
}
// If there was an error getting the data, tell the client
else if(this.state.error) {
return "Error loading data from API. Please reload the application or try again.";
}
// If all is well, the component should render the store with the correct initialState
else {
return (
<Provider store={createStore(reducers, this.state.initialState, applyMiddleware(ReduxThunk))}>
<View style={{ flex: 1 }}>
<RouterComponent />
</View>
</Provider>
);
}
}
}
export default App;
I hope this helps.
Its better to use server-rendering.
counterApp.js
export const counterApp = (state = {}, action) => {
switch (action.type) {
default:
return state;
}
}
server.js
//this route will get called for initial page load or page refresh.
server.get('/', (req, res) => {
const counter = parseInt(20, 10) || 0
// demo state,It can be function calling database operation or API.
let preloadedState = { counter } ;
const store = createStore(counterApp, preloadedState);
const html = renderToString(
<Provider store={store}>
<App />
</Provider>
);
const finalState = store.getState()
res.send(renderFullPage(html, finalState))
});
RenderFullPage :
function renderFullPage(html, preloadedState) {
return `
<!doctype html>
<html>
<head>
<title>Redux Universal Example</title>
</head>
<body>
<div id="root">${html}</div>
<script>
// WARNING: See the following for security issues around embedding JSON in HTML:
// http://redux.js.org/recipes/ServerRendering.html#security-considerations
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
<script src="/static/bundle.js"></script>
</body>
</html>
`
}
In App.js,( going to be render on client side only.)
In App.js,you can access initial state using window.__APP_INITIAL_STATE__,
render(<App {...window.__APP_INITIAL_STATE__} />, document.getElementById('root'));
For server side rendering,you have to setup webpack or else you prefer.