I have a react-redux app running on aspnet core, with server side rendering using aspnet prerendering.
Lets say i make a programming error, where in child component I try to access a undefined prop because of a stupid typo.
import {Child} from './child'
export class Parent extends React.Component {
render () {
const someProp = {
something: "something"
};
return <Child someProp={someProp} />;
}
}
export class Child extends React.Component {
render() {
return <div>this.props.someprop.something</div>;
//typo: should be someProp instead of someprop
}
Without server rendering I would have got an error similar to this: cannot access something of undefined at line x:yy
But with serverrendering i get a:
An unhandled exception occurred while processing the request.
Exception: Call to Node module failed with error: Prerendering timed out after 30000ms because the boot function in 'ClientApp/src/boot-server' returned a promise that did not resolve or reject. Make sure that your boot function always resolves or rejects its promise. You can change the timeout value using the 'asp-prerender-timeout' tag helper.
this makes debugging quite hard, when you dont get any feedback on what went wrong.
Any one knows how to setup a reject if something fails ? or is it even possible to debug a server side rendered code ?
here is my boot-server file, tell me if you need some more files.
import * as React from 'react';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import configureStore from './store/configureStore';
import {getFormById} from './actions/getFormActions';
import {updateUserLocale} from './actions/userLocaleActions';
import FormResponder from './components/mainComponents/formResponder';
export default function renderApp (params) {
return new Promise((resolve, reject) => {
const store = configureStore();
store.dispatch(getFormById(params.data.id, params.data.config, params.data.authenticationToken));
store.dispatch(updateUserLocale(params.data.userLocale));
const app = (
<Provider store={ store }>
<FormResponder />
</Provider>
);
// Perform an initial render that will cause any async tasks (e.g., data access) to begin
renderToString(app);
// Once the tasks are done, we can perform the final render
// We also send the redux store state, so the client can continue execution where the server left off
params.domainTasks.then(() => {
resolve({
html: renderToString(app),
globals: {
initialReduxState: store.getState(),
authenticationToken: params.data.authenticationToken,
config: params.data.config
}
});
}, reject); // Also propagate any errors back into the host application
});
}
I have had similar experience working with Visual Studio 2017. I eventually realized that the diagnostic information for the original error(s) was actually in the Output window.
I have done som research and have come to the conclusion that is not possible for the time beeing to debug the initial server rendered code.
what i have done instead is to implement logic, so that i can disable server rendering.
this is how it looks like:
public async Task<IActionResult> Index(string id, string userLocale = "en", bool server = true)
{
Guid positionId;
if (!Guid.TryParse(id, out positionId))
{
throw new Exception("Invalid position id");
}
var token = await _apiClient.GetToken();
var formData = new ApplicationFormViewModel()
{
Id = positionId,
UserLocale = userLocale,
AuthenticationToken = token.AccessToken,
Server = server
};
return View(formData);
}
view.cshtml:
#{if (#Model.Server) {
<div
class="container"
id="react-app"
asp-prerender-module="ClientApp/src/boot-server"
asp-prerender-data="new {
Id = #Model.Id,
UserLocale = #Model.UserLocale,
AuthenticationToken = #Model.AuthenticationToken,
Config = new {
ApplicationPostUrl = #Url.Action("SaveApplication"),
AttachmentPostUrl = #Url.Action("UploadAttachment"),
FormGetUrl = #Url.Action("GetForm")
}
}"
asp-prerender-webpack-config="webpack.config.js" >
Loading...
</div>
}
else {
<script>
var id= '#Model.Id';
var config= {
applicationPostUrl: '#Url.Action("SaveApplication")',
attachmentPostUrl: '#Url.Action("UploadAttachment")',
formGetUrl: '#Url.Action("GetForm")'
};
var userLocale='#Model.UserLocale';
var authenticationToken='#Model.AuthenticationToken';
var server = false;
</script>
<div class="container" id="react-app">loading</div>
}
}
#section scripts {
<script src="~/dist/main.js" asp-append-version="true"></script>
}
boot-server.jsx:
export default function renderApp (params) {
return new Promise((resolve, reject) => {
const store = configureStore();
store.dispatch(getFormById(params.data.id, params.data.config, params.data.authenticationToken));
store.dispatch(updateUserLocale(params.data.userLocale));
const app = (
<Provider store={ store }>
<FormResponder />
</Provider>
);
// Perform an initial render that will cause any async tasks (e.g., data access) to begin
renderToString(app);
// Once the tasks are done, we can perform the final render
// We also send the redux store state, so the client can continue execution where the server left off
params.domainTasks.then(() => {
resolve({
html: renderToString(app),
globals: {
initialReduxState: store.getState(),
authenticationToken: params.data.authenticationToken,
config: params.data.config,
server: true
}
});
}, reject); // Also propagate any errors back into the host application
});
}
boot-client.jsx:
// Grab the state from a global injected into server-generated HTML
const {id, initialReduxState, authenticationToken, config, server, userLocale } = window;
if (server) {
// Get the application-wide store instance, prepopulating with state from the server where available.
const store = configureStore(initialReduxState);
// This code starts up the React app when it runs in a browser.
ReactDOM.render(
<Provider store={ store }>
<FormResponder authenticationToken={authenticationToken} config={config} />
</Provider>,
document.getElementById('react-app')
);
}
else {
const store = configureStore();
store.dispatch(getFormById(id, config, authenticationToken));
store.dispatch(updateUserLocale(userLocale));
render(
<Provider store ={store}>
<FormResponder authenticationToken={authenticationToken} config={config} />
</Provider>,
document.getElementById('react-app')
); // Take our FormBuilder component and attach it with DOM element "app"
}
so now i can simply turn of server rendering by adding a ?server=false at the end of the url, and start debugging :)
Found a solution that works for me:
I inserted a try/catch on final renderToString.
where in catch i send a dispatch with the error.
updated boot-server.jsx
params.domainTasks.then(() => {
let html;
try {
html = renderToString(app);
}
catch (err) {
store.dispatch(loadFormFailed( {message: err.toString() } ));
}
resolve({
html: html,
globals: {
initialReduxState: store.getState(),
authenticationToken: params.data.authenticationToken,
config: params.data.config,
disableReactServerRendring: false
}
});
}, reject);
// Also propagate any errors back into the host application
});
Related
I am moving the project from react to nextjs and have a problem. I have a global wrapper that handles the login context and is using local storage.
I set "use client" directive at the top, but the component is trying to be rendered on the server too, and this code gives a hydration error:
export function AuthProvider(props) {
const initialParsedToken = typeof localStorage !== 'undefined' ? localStorage.getItem("jwt") : null
if (initialParsedToken) {
const decodedToken = jwtDecode<JwtInterface>(initialParsedToken);
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem("token");
} else {
initialState.user = decodedToken;
}
}
const [state, dispatch] = useReducer(authReducer, initialState);
if (typeof localStorage === 'undefined'){
return null
}
const login = (userData: any) => {
localStorage.setItem("jwt", userData.token);
dispatch({ type: "LOGIN", payload: userData });
};
function logout() {
localStorage.removeItem("jwt");
dispatch({ type: "LOGOUT" });
}
return (
<AuthContext.Provider
value={{ user: state.user, login, logout }}
{...props}
/>
);
}
The error:
Hydration failed because the initial UI does not match what was rendered on the server.
How can i use the useReducer hook in next, or make auth logic work with this framework?
I was planning to make render static content as server components, having client parts, but can I avoid using ugly useffect hack in each client component and just add a use client in nested dynamic content?
I tried using useffect hack in parent component, but nested components still don't work.
You should check on the type of window instead to make sure you are on the client side
if (typeof window !== undefined){
....your local storage logic
}
Also, you should consider moving your token to cookies instead of local storage as it doesn't cause conflicts and you can check your cookies on the server side using the context parameter, see this
Created a next.js full stack application. After production build when I run next start it returns 500 : internal server. I'm using environment varibles for hitting api.
env.development file
BASE_URL=http://localhost:3000
It was working fine in development
service.ts
import axios from 'axios';
const axiosDefaultConfig = {
baseURL: process.env.BASE_URL, // is this line reason for error?
headers: {
'Access-Control-Allow-Origin': '*'
}
};
const axio = axios.create(axiosDefaultConfig);
export class Steam {
static getGames = async () => {
return await axio.get('/api/getAppList');
};
}
Do you have a next.config.js file?
To add runtime configuration to your app open next.config.js and add the publicRuntimeConfig and serverRuntimeConfig configs:
module.exports = {
serverRuntimeConfig: {
// Will only be available on the server side
mySecret: 'secret',
secondSecret: process.env.SECOND_SECRET, // Pass through env variables
},
publicRuntimeConfig: {
// Will be available on both server and client
staticFolder: '/static',
},
}
To get access to the runtime configs in your app use next/config, like so:
import getConfig from 'next/config'
// Only holds serverRuntimeConfig and publicRuntimeConfig
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
// Will only be available on the server-side
console.log(serverRuntimeConfig.mySecret)
// Will be available on both server-side and client-side
console.log(publicRuntimeConfig.staticFolder)
function MyImage() {
return (
<div>
<img src={`${publicRuntimeConfig.staticFolder}/logo.png`} alt="logo" />
</div>
)
}
export default MyImage
I hope this helps.
I dont think you have setup env.
You need to configure it for it to work. Try it without it and it should work fine!
I have the following types:
DataService - Gets data from the server using signalr hub.
AppComponent - which is the entry point for my main application
The data service constructor is as follows.
constructor(private http: Http) {
var hub = $.connection.spwHub;
hub.client.loadEmployees = this.loadEmployees;
$.connection.hub.start().done(() => {
...
});
}
My AppComponent is as follows:
constructor(service: DataService) {
this.company = service.getCompany();
service.getEmployees().then(employees => this.employees = employees);
this.departments = service.getDepartments();
}
I get the following error of course because the hub async call has not returned before the hub connection is made.
EXCEPTION: Error in ./AppComponent class AppComponent_Host - inline template:0:0 caused by: SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.
What is the best way to deal with this issue in AngularJs2?
You can use the APP_INITIALIZER hook to perform logic, get something prepped, whatever, that you need before the rest of the application runs.
In your app.module.ts (or whatever your main module is):
import { APP_INITIALIZER, NgModule } from "#angular/core";
export function init_app(employeeService: EmployeeService) {
return () => employeeService.getEmployees();
}
#NgModule({
<...>
providers: [EmployeeService, {
provide: APP_INITIALIZER,
useFactory: init_app,
deps: [ EmployeeService ],
multi: true
}]
})
export class AppModule { }
The service is returning a Promise which will be automatically handled:
getEmployees() {
return <...function stuff ...>
.toPromise();
}
And here's the github issue where this is documented (no doc on the angular.io site yet): https://github.com/angular/angular/issues/9047
After searching and finding nothing I gave the idea that components that don't need to be loaded should probably be deferred by default. This means that the answer is a no-brainer.
// start.component.ts
constructor() {
// Start the connection
var hub = $.connection.spwHub;
$.connection.hub.start().done(() => {
// This loads the next component and runs the constructor
this.initialized = true;
});
}
// start.component.html
<div *ngIf="initialized">
<main-component></main-component>
<div>
// This type is lazy loaded as soon as the initialized equals true.
// main.component.ts
constructor(employeeService: EmployeeService) {
// Finally, load the needed data.
this.employees = employeeService.getEmployees();
}
I have the following simplified Component for a dashboard. The dashboard object is injected via props. The handleDeleteDashboard action checks if the dashboard isn't the last available one. If it is, you are not allowed to delete it. For this check I need nrOfDashboards which I get from the store in mapStateToProps. So I connected the Component to the redux store.
class Dashboard extends Component {
constructor(props) {
super(props);
this.handleDeleteDashboard = this.handleDeleteDashboard.bind(this);
}
handleDeleteDashboard() {
const { dashboardDeleteAction, dashboard, nrOfDashboards } = this.props;
if (nrOfDashboards < 2) {
// NOT Allowed to delete
} else {
dashboardDeleteAction(dashboard.id);
}
}
render() {
const { dashboard } = this.props;
return (
<Content>
<h1>{dashboard.name}</h1>
<Button onButtonClick={this.handleDeleteDashboard}>Delete</Button>
</Content>
);
}
}
Dashboard.propTypes = {
dashboard: customPropTypes.dashboard.isRequired,
nrOfDashboards: PropTypes.number.isRequired
};
function mapStateToProps(state) {
return {
nrOfDashboards: selectNrOfDashboards(state)
}
}
export default connect(mapStateToProps, { dashboardDeleteAction: dashboardActionCreators.dashboardDelete })(Dashboard);
But now the Component is subscribed to the store and updates whenever nrOfDashboards changes (I know I can perform a shouldComponentUpdate here to prevent if from re-rendering but that is not the point). So I am basically subscribing to changes on nrOfDashboards although I only need this information when I actively click on the delete button.
So I came up with a alternative solution where I disconnected the Component from the store and access the store via context in the handleDeleteDashboard method.
class Dashboard extends Component {
constructor(props) {
...
}
handleDeleteDashboard() {
const { dashboardDeleteAction, dashboard } = this.props;
const store = this.context;
if (selectNrOfDashboards(store.getState()) < 2) {
// NOT Allowed to delete
} else {
dashboardDeleteAction(dashboard.id);
}
}
render() {
...
}
}
Dashboard.propTypes = {
dashboard: customPropTypes.dashboard.isRequired,
};
Dashboard.contextTypes = {
store: PropTypes.object
};
export default connect(null, { dashboardDeleteAction: dashboardActionCreators.dashboardDelete })(Dashboard);
This works fine for me and whenever I actively click the button I ensure to get the fresh state from the store. Anyhow, I have not seen this technique somewhere else before and also read somewhere that accessing the store should not be done outside of mapStateToProps. But my question is if direct access to the store on demand is an anti-pattern and if I better should follow code example one, where I connect the Component to the store?
Yes. Direct access to the store is considered an anti-pattern. Idiomatic Redux code uses basic dependency injection - connect() and its mapState() and mapDispatch() arguments give you the data your component needs and the reference to dispatch, and middleware like Redux-Thunk gives you access to getState() and dispatch() in your action creators.
Ideally, your component would simply dispatch an action creator, and let the action creator logic worry about whether or not to really dispatch a real action. So, in your case, that might look like:
// action creator
export function deleteDashboard(dashboardID) {
return (dispatch, getState) => {
const state = getState();
const numberOfDashboards = selectNumberOfDashboards(state);
if(numberOfDashboards >= 2) {
dispatch({
type : "DELETE_DASHBOARD",
payload : {
dashboardID
}
});
}
}
}
// component
handleDeleteDashboard() {
const {dashboard} = this.props;
this.props.dispatch(deleteDashboard(dashboard.id));
}
See the Redux FAQ question on this topic: http://redux.js.org/docs/FAQ.html#store-setup-multiple-stores
So, I see on an error, redux-promise hands me back error: true, along with the payload, but that is once it hits the reducer... to me, decoupling the request AND error condition is a bit odd, and seems inappropriate. What is an effective way to also deal with error condition when using axios w/ reduc-promise (middleware).. here is the gist of what i have..
in action/
const request = axios(SOME_URL);
return {
type: GET_ME_STUFF,
payload: request
}
in reducer/
const startState = {
whatever: [],
error: false
}
case GET_ME_STUFF:
return {...state, startState, {stuff:action.payload.data, error: action.error? true : false}}
etc... then I can deal with the error.. so, my api call is now split into two seperate areas and that seems wrong.... there must be something I am missing here. I would think in the /actions I can pass in a callback that handles a new action etc.. or something, but not split it.
I've had to go through a similar situation. The challenge is that you likely won't be able to evaluate the results of the promise until it is at the reducer. You could handle your exceptions there but it's not the best pattern. From what I've read reducers are meant only to return appropriate pieces of state based on action.type and do nothing else.
So, enter an additional middleware, redux-thunk. Instead of returning an object, it returns a function, and it can coexist with promise.
It's explained quite well at http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/ [archived here]. Essentially, you can evaluate the promise here and dispatch through the other action creators before the promise result hits the reducers.
In your actions file, add additional action creators that would handle the success and error (and any other) states.
function getStuffSuccess(response) {
return {
type: GET_ME_STUFF_SUCCESS,
payload: response
}
}
function getStuffError(err) {
return {
type: GET_ME_STUFF_ERROR,
payload: err
}
}
export function getStuff() {
return function(dispatch) {
axios.get(SOME_URL)
.then((response) => {
dispatch(getStuffSuccess(response))
})
.catch((err) => {
dispatch(getStuffError(err))
})
}
}
return null
This is roughly to how you might translate your pseudocode to what is explained at the link. This handles evaluating the promise directly in your action creator and firing off the appropriate actions and payloads to your reducers which follows the convention of action -> reducer -> state -> component update cycle. I'm still pretty new to React/Redux myself but I hope this helps.
The accepted answer doesn't make use of redux-promise. Since the question is actually about handling errors using redux-promise I provide another answer.
In the reducer you should inspect the existence of the error attribute on the action object:
// This is the reducer
export default function(previousState = null, action) {
if (action.error) {
action.type = 'HANDLE_XHR_ERROR'; // change the type
}
switch(action.type) {
...
And change the type of the action, triggering a state change for an error handling component that you have set up for this.
You can read a bit more about it here on github.
It looks like you can catch the error where you make the dispatch, then make an separate error dispatch if it happens. It's a bit of a hack but it works.
store.dispatch (function (dispatch) {
dispatch ({
type:'FOO',
payload:axios.get(url)
})
.catch (function(err) {
dispatch ({
type:"FOO" + "_REJECTED",
payload:err
});
});
});
and in the reducer
const reducer = (state=initialState, action) => {
switch (action.type) {
case "FOO_PENDING": {
return {...state, fetching: true};
}
case "FOO_REJECTED": {
return {...state, fetching: false, error: action.payload};
}
case "FOO_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
data: action.payload,
};
}
}
return state;
};
Still using redux-promises you can do something like this which I think is an elegant way to deal with this problem.
First, set a property in the redux state that will hold any ajax errors that may occurred.
ajaxError: {},
Second, setup a reducer to handle ajax errors:
export default function ajaxErrorsReducer(state = initialState.ajaxError, action) {
if (action.error) {
const { response } = action.payload;
return {
status: response.status,
statusText: response.statusText,
message: response.data.message,
stack: response.data.stack,
};
}
return state;
}
Finally, create a very simple react component that will render errors if there are any (I am using the react-s-alert library to show nice alerts):
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Alert from 'react-s-alert';
class AjaxErrorsHandler extends Component {
constructor(props, context) {
super(props, context);
this.STATUS_GATE_WAY_TIMEOUT = 504;
this.STATUS_SERVICE_UNAVAILABLE = 503;
}
componentWillReceiveProps(nextProps) {
if (this.props.ajaxError !== nextProps.ajaxError) {
this.showErrors(nextProps.ajaxError);
}
}
showErrors(ajaxError) {
if (!ajaxError.status) {
return;
}
Alert.error(this.getErrorComponent(ajaxError), {
position: 'top-right',
effect: 'jelly',
timeout: 'none',
});
}
getErrorComponent(ajaxError) {
let customMessage;
if (
ajaxError.status === this.STATUS_GATE_WAY_TIMEOUT ||
ajaxError.status === this.STATUS_SERVICE_UNAVAILABLE
) {
customMessage = 'The server is unavailable. It will be restored very shortly';
}
return (
<div>
<h3>{ajaxError.statusText}</h3>
<h5>{customMessage ? customMessage : ajaxError.message}</h5>
</div>
);
}
render() {
return (
<div />
);
}
}
AjaxErrorsHandler.defaultProps = {
ajaxError: {},
};
AjaxErrorsHandler.propTypes = {
ajaxError: PropTypes.object.isRequired,
};
function mapStateToProps(reduxState) {
return {
ajaxError: reduxState.ajaxError,
};
}
export default connect(mapStateToProps, null)(AjaxErrorsHandler);
You can include this component in your App component.
This might not be the best approach but it works for me. I pass the 'this' of my component as var context. Then when i get response back i just execute the methods defined in my components context. In my component i have successHdl and errorHdl. From there i can trigger more redux actions as normal. I checked all the previous answers and seem too daunting for such a trivial task.
export function updateJob(payload, context){
const request = axios.put(UPDATE_SOMETHING, payload).then(function (response) {
context.successHdl(response);
})
.catch(function (error) {
context.errorHdl(error);
});;
return {
type: UPDATE_SOMETHING,
payload: payload,
}
}
Don't use redux-promise. It overcomplicates something that's actually super simple to do yourself.
Instead read the redux docs: http://redux.js.org/docs/advanced/AsyncActions.html
It'll give you a much better understanding of how to handle this kind of interactions and you'll learn how to write something (better than) redux-promise yourself.