React SSR - Rendering a component following an async action - javascript

React's component lifecycle goes hand in hand with the DOM, so when I'm trying to render on the server side following an async action I'm encountering issues.
I have a component (let's call it the containing component) which is responsible for dynamically importing another component (let's call it the inner component) according to data passed in its props.
Once the import promise is resolved, I'd like to render the inner component inside of the containing component.
The problem is, I have no way of accessing the containing component's lifecycle.
render() is only triggered once upon the construction of the containing component.
Updating the component via forceUpdate() and setState() are not possible for the same reason componentDidMount() will never be called.
How do I render a component following an async action on the server side when using server rendering?
My code so far:
import React from 'react';
export default class ComponentLoader extends React.Component<{ component: string }, { component: any }> {
constructor(props) {
super(props);
this.state = {
component: null
}; //no-op
}
componentDidMount(): void {
//never called
}
componentWillMount(): void {
import('./' + this.props.component).then(module => {
this.forceUpdate(); //no-op
this.setState({ component: React.createElement(module.default) }); //no-op
});
}
render() {
//called only once during construction
return this.state.component || null; //this.state won't be available, nor will it matter if I access component any other way since render() won't be called again once that component is resolved anyway.
}
}

I ended up using loadable components, a different implementation than what you see in the comments though.
https://github.com/jamiebuilds/react-loadable
https://github.com/konradmi/react-loadable-ssr-code-splitting
On the server side it makes sure to only render the react components once all the dependencies are loaded async. That way the render is sync since that's the only thing that's supported at the moment (though there's a feature request for supporting async react ssr), but the loading is done async, so it doesn't block any other operations.

Related

React reuses components instead of creating new so that I have to rely on componentWillReceiveProps to load fresh data

I have a problem with ReactJS as when in parent component's state that stores child components (as thumbnails) the child components stored as array of components are constructed once and have componentDidMount called once. Once I 'reimport' data and create new child components based on new data from backend API (for example upon new sorting mode activated) the components do not call componentDidMount and i have to rely on componentWillReceiveProps method to import for example a link to the picture to be displayed on a thumbnail (it seems like react reuses components). If for example the the data in child components is being imported slowly it shows and old photo because remembers previous iteration done in own componentDidMount done after creation.
How can i force react to always create new child components from the scratch and thanks to that achieve having componentDidMount called to include data import from backend and avoid relying on componentWillReceiveProps call?
Here is the pseudocode where parent component ComponentManager imports person data from backend and creates thumbnails based on retrieved JSON. Thenafter it can upodate thumbnails after user changes sorting order:
class ComponentManager extends Component {
constructor(props) {
super(props);
this.state = {
personsThumbnails : undefined
}
}
componentDidMount() {
// Import person ids and create SinglePersonThumbnail(s) child components as the state personsThumbnails
importPersonsIds();
}
importPersonsIds(sortingMode) {
// Importing persons data from backend API and created thumbnails stored in personsThumbnails state
...
}
render() {
return(
<div>
<button onClick={()=>{this.importPersonsIds("SORT_BY_AGE")}}>Sort by age</button>
<button onClick={()=>{this.importPersonsIds("SORT_BY_NAME)}}>Sort by name</button>
<div>
{this.state.personsThumbnails}
</div>
</div>
);
}
}
class SinglePersonThumbnail extends Component {
constructor(props) {
super(props);
this.state = {
photoUrl : undefined,
personsName : undefined
}
}
componentDidMount() {
// Called when component is created
this.importDataAndPhotoForPerson(this.props.id);
}
componentWillReceiveProps(nextProps) {
// Called always when ComponentManager changes the order of thumbnails upon other sorting mode triggered
this.importDataAndPhotoForPerson(this.props.id);
}
importDataAndPhotoForPerson(id) {
// Imports name of the person and link to photo stored in state
}
render() {
return(
// Display image by link and person's name based on photoUrl and personsName states!
);
}
}
you can use componentDidUpdate() and can see the documentation from official site.
componentWillReceiveProps is outdated and not recommended. A better idea will be to store the data from backend in some context/redux. This will cause the child components to re-render with updated data.

React component doesn't show correct render count [duplicate]

I am new to react and I am trying to develop a simple web app with it but I get an error.
My Constructor is called twice when I load a class component can you help?
Home.js
import React from 'react'
import Land from "../Land";
function Home() {
return (
<div>
<h1>Home!</h1>
<Land/>
</div>
)
}
export default Home
Partial Land.js
import React, { Component } from 'react'
import Login from "./Login";
class Land extends Component {
constructor(props) {
super(props)
this.state = {
}
console.log("LAND")
}
the log LAND is hit twice.
In some of the components I wish to make an API call that hits a DB but I only want to hit it once.
In many instances using componentDidMount is not convenient because props only appear after componentDidMount therefor id like to do the call in render(I will not be using setState, that would cause a reload of render).
Thanks in advance
You are using <StrictMode/> and it's development mode
While in <StrictMode/>, react will detect unexpected side effects which will call lifecycle functions more than once duration the development mode, will not be trigger twice in production.
From docs:
why is called twice
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
will not call twice
Note:
This only applies to development mode. Lifecycles will not be double-invoked in production mode.
I didn't quite get what are you trying to say in the comment, but you totally can call function that fetches data within componentDidMount hook
Here's an example:
https://codesandbox.io/s/react-calls-constructor-twice-q5gzs

How can componentDidMount wait for a value from redux store before executing (without using setTimeout)?

I'm trying to get the authentication info into React component and fetch data related to the authenticated user, but componentDidMount is not getting the auth value from the redux store unless setTimeOut is used.
How Do I go about it?
I tried componentWillReceiveProps() , but that does not work either .
class Dashboard extends Component {
componentDidMount = () => {
console.log(this.props.auth);
setTimeout(
() => console.log(this.props.auth),
100
)
}
Only console.log within setTimeout returns the value
I had this same issue a while back and what helped me was to think about my component as a function.
What I would do is display a spinner, until auth is not null, then render a different view once it is ready.
class Dashboard extends Component {
render = () => {
if(this.props.auth === null){
return <SpinnerComponent />
}
return ....
}
}
What will happen is that:
Before props.auth is ready, the component will render a spinner
After props.auth is ready, the component will render your normal app
Rerendering happens when props are change (ie redux is changed)

Understanding withtracker() component in Meteor

I was trying to learn meteor and not very familiar with this pattern of HOC (it's meteor.js with react).
I was going though their offical docs of tutorials. Here is what they did (You can click here to visit the page)
They imported following package in App.js
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
Then there is a simple to do class App extends component wrapped by this HOC
export default withTracker(() => {
return {
tasks: Tasks.find({}).fetch(),
};
})(App);
The official docs for the same says
The wrapped App component fetches tasks from the Tasks collection and
supplies them to the underlying App component it wraps as the tasks
prop. It does this in a reactive way, so that when the contents of the
database change, the App re-renders, as we'll soon see!
Now this language isn't exactly alien to me but I am having hard to comprehend and understand it. So Can someone explain me the same?
To be specific what is The wrapped App component fetches tasks and supplies it to underline app component
A higher order component is in the most basic form a function that takes a component type as input and returns a component class that wraps the input component and adds functionality to it.
Usually the signature is function that takes the argument to apply to the wrapped component which returns a HOC as described above so you can use it with multiple components.
Here is a very basic example that shows an error message if the component it's used on or any of it's child components throw an exception:
const catchError = ({ errorMessage }) => InputComponent => class extends Component {
render() {
try {
return <InputComponent {...this.props} />;
} catch {
return <div>{errorMessage}</div>
}
}
}
const ComponentWithErrorMessage = catchError({ errorMessage: 'Whoops!' })(MyComponent);
// This is the same as the following, the first just immediately invokes the returned function
const whoopsErrorHoc = catchError({ errorMessage: 'Whoops!' });
const ComponentWithWhoopsError = whoopsErrorHoc(MyComponent);
The meteor HOC will be a bit more complicated but the idea is the same. It receives a reference to the meteor Task store and will return a component that re-renders the input component whenever the data changes in the store and add the data to the props of that component.

React router loading data each time

I am new to using React and React Router.
The home page (home component) loads data via an ajax request to the server which displays the data.
Then people can navigate within the app using react router. But when someone goes back to the home page (home component) the ajax call is again made to the server which is unnecessary.
So basically when you're navigating within components using react router each time a component is loaded ajax calls are made every time.
Is there something we can do to maybe cache the data like in Angular where we don't have to make the ajax calls again and again when a component is loaded.
Thanks
class NewComponent extends React.Component {
constructor(){
super();
this.state = {
data : []
}
}
componentWillMount() {
let length = Object.keys(this.state.data).length;
if(length === 0){
let req = new Request('https://example.com/json', {
mode: 'cors',
method: 'get'
});
this.serverRequest = fetch(req).then(function (result) {
return result.json()
}).then(function(j){
this.setState({
data: j.up
})
}.bind(this));
}
}
render(){
return(
some html
)
}
You'll probably want to store all your state in your topmost app component, which should only re-mount when the page is reloaded. That way the state can stay in there, and be passed down to child components.
If it's a larger project, you might consider using a state management system alongside React like redux.
Another idea is to move your ajax functions out into their own module, which gets imported into your component. Then you can place some caching in your ajax module, away from your component.

Categories

Resources