问题
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.
回答1:
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.
回答2:
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.
回答3:
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.
来源:https://stackoverflow.com/questions/50315639/passing-initial-state-from-api-call-to-createstore-when-react-native-application