React App refreshes on prop change - reactjs

I am implementing Yahoo React-Intl to localize my application. In order to do this I must wrap the ROOT with the Localizer like so:
const Root = React.createClass({
childContextTypes: {
refresh: React.PropTypes.func
},
getChildContext() {
return {
refresh: () => this.forceUpdate()
}
},
render() {
let { language, messages } = this.props;
return (
<Provider store={store }>
<IntlProvider locale="en" messages= { messages } >
<Router history={browserHistory}>
</Router>
</IntlProvider>
</Provider>
)
}
}
function mapStateToProps(state) {
return {
language: state.currentLanguage.language,
messages: state.currentLanguage.messages
};
}
export default connect(mapStateToProps, {})(Root)
With "messages" being a JSON object containing key value translation mappings.
My problem is that I have to make this dynamic so that the user can choose to change languages. I have created an Action/Reducer combo in order to change objects assigned to the messages prop within the application state. However, when I call the action and feed messages a new object, the entire pages refreshes and my state goes back to initial values.
Messages by default is a null variable and is assigned an object containing id values to chinese characters. When given the object by default, the translations are seen correctly. It is only when I update it via action that the application refreshes and the desired results are not obtained.
What may be causing my application to refresh?

Related

Data Flow between two different react application

I have developed a react application in a modular way. Such that every module is an independent application. I need to pass the data from one route from application one to other routes on application 2.
The data that I need to pass is large in size.
Requesting the approach apart from window.location .
All of my applications are deployed on the same server on Linux.
You're best bet is to create a store that all of the routes have access to. If you were to use redux for example, you can keep sharedValue: '' in a root reducer. Then use mapStateToProps to each individual component for those routes.
Here is an example reducer.
function rootReducer(state = {sharedValue: ''}, action) {
switch (action.type) {
case UPDATE_SHARED_VALUE:
return Object.assign({}, state, {
sharedValue : action.payload
})
default:
return state
}
}
And example routes.
<Route path="/" component={App}/>
<Route path="path1" component={Component1}/>
<Route path="path2" component={Component2}/>
And, depending on how you want the components to receive the sharedValue, you can use mapStateToProps to share sharedValue with the components.
Component1
// Component1.js
// put the below outside of the component
function mapStateToProps(state) {
const { sharedValue } = state
return { sharedValue }
}
export default connect(mapStateToProps)(Component1)
Component2
// Component2.js
// put the below outside of the component
function mapStateToProps(state) {
const { sharedValue } = state
return { sharedValue }
}
export default connect(mapStateToProps)(Component2)
There is more to setting up redux but here you can see how both components have access to the shared value. Since it is in a shared store, the value should persist across both routes.

Context API consume from anywhere

I am trying to implement a shared state into my application using the React context api.
I am creating an errorContext state at the root of my tree. The error context looks like so:
// ErrorContext.js
import React from 'react';
const ErrorContext = React.createContext({
isError: false,
setError: (error) => {}
});
export default ErrorContext;
Desired Result
I would like to update (consume) this context from anywhere in the app (specifically from within a promise)
Ideally the consume step should be extracted into a exported helper function
Example Usage of helper function
http.get('/blah')
.catch((error) => {
HelperLibrary.setError(true);
})
Following the react context docs:
I can create a provider like so :
class ProviderClass {
state = {
isError: false,
setError: (error) => {
this.state.isError = error;
}
}
render() {
return (
<ErrorContext.Provider value={this.state}>
{this.props.children}
</ErrorContext.Provider>
)
}
}
Then I can consume this provider by using the Consumer wrapper from inside a render call:
<ErrorContext.Consumer>
{(context) => {
context.setError(true);
}}
</ErrorContext.Consumer>
The Problem with this approach
This approach would require every developer on my team to write lots of boilerplate code every-time they wish to handle a web service error.
e.g. They would have to place ErrorContext.Consumer inside the components render() method and render it conditionally depending on the web service response.
What I have tried
Using ReactDOM.render from within a helper function.
const setError = (error) =>{
ReactDOM.render(
<ErrorContext.Consumer>
// boilerplate that i mentioned above
</ErrorContext.Consumer>,
document.getElementById('contextNodeInDOM')
) }
export default setError;
Why doesn't this work?
For some reason ReactDOM.render() always places this code outside the React component tree.
<App>
...
<ProviderClass>
...
<div id="contextNodeInDOM'></div> <-- even though my node is here
...
</ProviderClass>
</App>
<ErrorContext.Consumer></ErrorContext.Consumer> <-- ReactDOM.render puts the content here
Therefore there is no context parent found for the consumer, so it defaults to the default context (which has no state)
From the docs
If there is no Provider for this context above, the value argument
will be equal to the defaultValue that was passed to createContext().
If anyone can assist me on my next step, I am coming from Angular so apologies if my terminology is incorrect or if I am doing something extremely stupid.
You can export a HOC to wrap the error component before export, eliminating the boilerplate and ensuring that the context is provided only where needed, and without messing with the DOM:
// error_context.js(x)
export const withErrorContext = (Component) => {
return (props) => (
<ErrorContext.Consumer>
{context => <Component {...props} errorContext={context} />}
</ErrorContext.Consumer>
)
};
// some_component.js(x)
const SomeComponent = ({ errorContext, ...props }) => {
http.get('/blah')
.catch((error) => {
errorContext.setError(true);
})
return(
<div></div>
)
};
export default withErrorContext(SomeComponent);
Now that React 16.8 has landed you can also do this more cleanly with hooks:
const SomeComponent = props => {
const { setError } = useContext(ErrorContext)
http.get("/blah").catch(() => setError(true))
return <div />
}
Following the react context docs:
I can create a provider like so :
class ProviderClass {
state = {
isError: false,
setError: (error) => {
this.state.isError = error;
}
}
I don't think so - there should be setState used. There is a general rule in react "don't mutate state - use setState()" - abusing causes large part of react issues.
I have a feeling you don't understand context role/usage. This is more like a shortcut to global store eliminating the need of explicitly passing down props to childs through deep components structure (sometimes more than 10 levels).
App > CtxProvider > Router > Other > .. > CtxConsumer > ComponentConsumingCtxStorePropsNMethods
Accessing rendered DOM nodes with id is used in some special cases, generally should be avoided because following renders will destroy any changes made externally.
Use portals if you need to render sth somewhere outside of main react app html node.

React Redux Url Change Events / Architecture

I'm building an application with React. If someone wants to search the following happens:
State is updated: this.props.Address.selectedAddress
Click on a search button the search event is trigger AND my URL is changed:
this.props.handleSearch(address);
let city = format_city(this.props.Address.selectedAddress);
browserHistory.push('/searchfor/'+city);
That works fine. Now I would like that a search event is also triggered if someone puts a new url in the application. What I did so far:
class ResultsList extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.unlisten = browserHistory.listen( (location) => {
console.log('route changes');
if (this.selectedAdressNOTMatchUrl()){
this.handelAdressSearch()
}
});
}
componentWillUnmount() {
this.unlisten();
}
Now the problem that the click event changes the url and the result list component thinks it have to handle this event: triggering search and updateting selectedAddress.
I really think it is a bad design how I have done it. Of course I could add a timestamp or something like this to figure out if browserHistory.push('/searchfor/'+city); happend right before the url change event is fired, but that is a bit ugly.
Every idea is appreciated!
Here is my setup:
export const store = createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(
applyMiddleware(
thunkMiddleware,
promiseMiddleware()),
autoRehydrate()
)
);
const persistor = persistStore(store, {storage: localForage});
render(
<Provider store={store} persistor={persistor}>
<Router history={browserHistory}>
<Route path="/" component={Home}/>
<Route path="/searchfor/:city" component={Suche}/>
</Router>
</Provider>,
PS: One more remark
I tried react-router-redux. It saves the url state. BUT it doese not solve the issue. If for example I enter a new url. Then the Event LOCATION_CHANGE is fired. BUT short after that, my persist/REHYDRATE event is fired. So that the url changes back. That makes the behavior even worse...
PPS: To make it more clear. Everything works fine. I want to add one more feature. If the users enters a new url himself, then the application should handle this the same way if he clicked on the search button. BUT it seems a bid of hard, since in that case in my application the rehydrate event is fired...
Not sure how redux-persist ("rehydrating") works so maybe I'm missing something crucial.. But I'm thinking it could be possible to solve your issue through connecting your ResultsList component to Redux so that it passes the react-router url-parameter to it, something like this
// ownProps is be the props passed down from the Route, including path params
const mapStateToProps = (state, ownProps) => {
return {
searchTerm: ownProps.city, // the url param
results: state.searchResults // after a manual url entry this will be undefined
}
}
const mapDispatchToProps = (dispatch) => {
return {
handleSearch: searchTerm => dispatch(searchAction(searchTerm))
};
};
(That kind of code should be possible according to these docs here)
Then, inside the ResultsList, you could initiate the search.
componentDidMount() {
const {searchTerm, handleSearch} this.props;
handleSearch(searchTerm);
}
}
render() {
const {results} = this.props;
return !results ?
<p>Loading..</p> :
<div>
{ results.map(renderResultItem) }
</div>
}
That way the component would not have to care if the URL-param came from a manually entered url, or from a programmatically pushed URL from the search page.

What are the sequence of events when loading xhr data into a react redux routed page?

I'm a react/redux noob, and am getting myself confused.
Here's what I want to achieve:
/myapp/#/
global navigation with links to /myapp/#/ and /myapp/#/products
/myapp/#/products
global navigation (as above)
list of products retrieved from axios GET XHR request. Each product links to /myapp/#/products/{productId}
/myapp/#/products/{productId}
global navigation (as above)
list of products (as above)
product details panel which shows additional details retrieved from another axios GET XHR request
User should be able to click products in the list to view details in the inset product details panel, but the list XHR request should not be re-requested.
My Code
/index.js
...
//I don't think this is the right place for this code...
function loadProductListData() {
store.dispatch(fetchListData('/api/products'))
}
render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={HomePage} />
<Route path="/products(/:productId)" component={ProductsPage} onEnter={loadProductListData} />
</Router>
</Provider>,
document.getElementById('root')
)
...
/components/menu.js
import React from 'react'
import { Link } from 'react-router';
const Menu = () => (
<ul>
<li><Link to={''}>Home</Link></li>
<li><Link to={'products'}>Products</Link></li>
</ul>
)
export default Menu
/components/productList.js
import React from 'react'
import { Link } from 'react-router';
class List extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<ul>
{
this.props.products.map(product =>
this.renderRow(product)
)
}
</ul>
)
}
renderRow (product) {
return (
<li key={product.id}><Link to={'products/' + product.id}>{product.name}</Link></li>
)
}
}
export default List
/actions.js
export const REQ_DATA = 'REQ_DATA';
export const RECV_DATA = 'RECV_DATA';
import axios from 'axios'
function requestData() {
return {type: types.REQ_DATA}
}
function receiveData(json) {
return{
type: types.RECV_DATA,
data: json
}
}
export function fetchListData(url) {
return function(dispatch) {
dispatch(requestData())
return axios({
url: url,
timeout: 20000,
method: 'get',
responseType: 'json'
})
.then(function(response) {
dispatch(receiveData(response.data))
})
.catch(function(response){
//
})
}
}
My code above works, but I'm now in a tangle about what to do next. I'm not sure I should be using onEnter on the route to trigger the XHR request because the link to the product details page goes through the same route, and fires the same XHR for list data.
My Question
Instead of using Link, should I use an onClick handler which fires the XHR, updates state (causing component re-render), and then push to a new route manually? Is it correct for the link to simply re-route, and possible for everything else to be triggered as a result of the routing? As I say, I don't want to re-load the list component when re-routing.
Any other general advice on approach and architecture for an application like this would be gratefully received.
You should rather dispatch the loadList action within componentWillMount() and implement a shouldComponentUpdate callback within your List component to control wether the component should be rendered/updated again or not - based on your state.
So for instance:
shouldComponentUpdate(nextProps, nextState) {
return this.props.products !== nextProps.products;
}
Check the docs at https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate
Then use "301 - Not Modified" headers on your backend to not always return the same data if nothing has been changed within your products list.
For loading the product detail you could dispatch your detail loading action within componentWillReceiveProps like this:
componentWillReceiveProps(nextProps) {
if (this.props.params.id !== nextProps.params.id) {
this.props.dispatch(fetchDetailProduct(nextProps.params.id));
}
}
This way you are checking if the user clicked on a different product id. If so, the action to load this detail data is dispatched. The component will be updated with your new product detail data based on your redux flow.
As an architectural advice, i would also suggest you to give your actions better names like 'PRODUCT/FETCH_LIST', 'PRODUCT/FETCH_LIST_RECEIVED', 'PRODUCT/FETCH_LIST_ERROR'.
I'd also suggest using Immutable.js (https://facebook.github.io/immutable-js) for your state. It will be much easier to identify updates within your state, since it provides some efficient equals() checks for all the objects. With an Immutable.js data structure, your shouldComponentUpdate will look like this:
shouldComponentUpdate(nextProps, nextState) {
return !this.props.products.equals(nextProps.products);
}

Prevent react-native-router-flux from rendering all components

I'm using React-Native-Router-Flux for routing my app. The issue is that it seems like when a redux state changes, ALL the components under the Router gets rerendered, not just the "current" component.
So lets say I have 2 components under the Router: Register and Login and both share the same authenticationReducer. Whenever an authentication event (such as user registration or signin) fails, I want to display error Alerts.
The problem is that when an error is fired from one of the components, two Alerts show up at the same time, one from each component. I assumed when I am currently on the Register scene, only the error alert would show from the Register component.
However, it seems like both components rerender whenever the redux state changes, and I see 2 alerts (In the below example, both 'Error from REGISTER' and 'Error from SIGNIN').
Here are the components:
main.ios.js
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Router>
<Scene key='root'>
<Scene key='register' component={Register} type='replace'>
<Scene key='signin' component={SignIn} type='replace'>
</Scene>
</Router>
</Provider>
);
}
}
Register.js
class Register extends Component {
render() {
const { loading, error } = this.props;
if (!loading && error) {
Alert.alert('Error from REGISTER');
}
return <View>...</View>;
}
}
const mapStateToProps = (state) => {
return {
loading: state.get("authenticationReducer").get("loading"),
error: state.get("authenticationReducer").get("error"),
};
};
export default connect(mapStateToProps)(Register);
SignIn.js
class SignIn extends Component {
render() {
const { loading, error } = this.props;
if (!loading && error) {
Alert.alert('Error from SIGNIN');
}
return <View>...</View>;
}
}
const mapStateToProps = (state) => {
return {
loading: state.get("authenticationReducer").get("loading"),
error: state.get("authenticationReducer").get("error"),
};
};
export default connect(mapStateToProps)(SignIn);
How do I change this so that only the REGISTER error message shows when I am currently on the Register Scene, and vice versa?
Thanks
Because of the way react-native-router-flux works, all previous pages are still "open" and mounted. I am not totally sure if this solution will work, because of this weird quirk.
Pretty strict (and easy) rule to follow with React: No side-effects in render. Right now you are actually doing a side-effect there, namely, the Alert.alert(). Render can be called once, twice, whatever many times before actually rendering. This will, now, cause the alert to come up multiple times as well!
Try putting it in a componentDidUpdate, and compare it to the previous props to make sure it only happens once:
componentDidUpdate(prevProps) {
if (this.props.error && this.props.error !== prevProps.error) {
// Your alert code
}
}
I am not totally convinced that this will actually work, as the component will still update because it is kept in memory by react-native-router-flux, but it will at least have less quirks.
I solved this by creating an ErrorContainer to watch for errors and connected it to a component that uses react-native-simple-modal to render a single error modal throughout the app.
This approach is nice because you only need error logic and components defined once. The react-native-simple-modal component is awesomely simple to use too. I have an errors store that's an array that I can push errors to from anywhere. In the containers mapStateToProps I just grab the first error in the array (FIFO), so multiple error modals just "stack up", as you close one another will open if present.
container:
const mapStateToProps = (
state ) => {
return {
error: state.errors.length > 0 ? state.errors[0] : false
};
};
reducer:
export default function errors (state = [], action) {
switch (action.type) {
case actionTypes.ERRORS.PUSH:
return state.concat({
type: action.errorType,
message: action.message,
});
case actionTypes.ERRORS.POP:
return state.slice(1);
case actionTypes.ERRORS.FLUSH:
return [];
default:
return state;
}
}

Resources