I want to do code-splitting manually using preact. Preact already splits code for routes, but I want to do it myself.
My use case is that I am building a tool where a user can add widgets to a dashboard. On the home page I only want to include the code for the widgets that the user has configured, not the ones the user has not used.
So I do not want to have the code for all widgets bundled in the bundle.js, but request it lazily when needed, when rendering the list of widgets.
I have attempted to use the async! syntax, which I saw in some old commits for the boiler plate, but that did not work.
A simplified example of my code
The configuration data
[{ "type": "notes", "title": "Widget 1}, { "type": "todo", "title": "Widget 2"}]
The render function of the list
const Grid = ({ widgets }) => (
<ul>
{widgets.map((widget) => <li key={widget.title}><Widget widget={widget} /></li>)}
</ul>
);
Widget component
Here I have a mapping from type to component:
import notes from widgets/notes;
import todo from widgets/todo;
class Widget extends Component {
widgetMap(widget) {
if (widget.type === 'notes') {
return notes;
}
if (widget.type === 'todo') {
return todo;
}
}
render ({ widget }) {
const widgetComponent = this.widgetMap(map);
return (
<div>
<h1>{widget.title}</h1>
<widgetComponent />
</div>
);
}
}
If you are using Preact X, it features <Suspense> and lazy which is same API React also uses. More about it in depth you can read here: https://reactjs.org/docs/concurrent-mode-suspense.html
Your example, modified would look like this (code adjusted from here):
import { Suspense, lazy } from `preact/compat`;
const notes = lazy(() => import('./widgets/notes'));
const todo = lazy(() => import('./widgets/todo'));
class Widget extends Component {
widgetMap(widget) {
if (widget.type === 'notes') {
return notes;
}
if (widget.type === 'todo') {
return todo;
}
}
render ({ widget }) {
const widgetComponent = this.widgetMap(map);
return (
<Suspense fallback={<div>loading...</div>}>
<div>
<h1>{widget.title}</h1>
<widgetComponent />
</div>
</Suspense>
);
}
}
For older version of Preact, you can put together async loading HOC yourself as long as you have Babel or some other transpiler set up to handle dynamic module loading
export default asyncComponent = (importComponent) => {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = { component: null };
}
async componentDidMount() {
const { default: component } = await importComponent();
this.setState({ component });
}
render() {
const Component = this.state.component;
return Component ? <Component {...this.props} /> : <div>loading...</div>;
}
}
return AsyncComponent;
}
Related
I am working with some legacy code moved into my new react product. Essentially I have a large grouping of widgets (a lot!) so my employer has asked me NOT to rewrite them but work with them.
I have a BaseWidget:
import { Select } from 'layout/containers'
export default class BaseWidget extends React.Component {
onChange = e => {
this.props.onChange({ target: { value: e } });
};
get operators() { return .... }
get value() { return .... }
get options() {
const { options } = this;
return options;
}
render() {
const multi = this.operators.includes('in');
return (
<div>
<label>{this.props.label}</label>
<Select
value={this.value}
onChange={this.onChange}
options={this.options}
/>
</div>
);
}
}
And I need an AutocompleteWidget which extends from BaseWidget:
import { getAutocompleteOptions } from 'options/modules/autocomplete';
class AutocompleteWidget extends BaseWidget {
onChange = e => {
this.props.onChange({ target: { value: e } });
};
get options() {
return ['options0', 'options1', 'options2'];
}
}
const mapStateToProps = ({ autocomplete }) => {
return {
autocomplete,
};
};
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getAutocompleteOptions,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(AutocompleteWidget);
I want to use AutocompleteWidget in a specific widget class let's say AssetWidget:
export default class AssetWidget extends AutocompleteWidget {
get options() {
return ['asset0', 'asset1', 'asset2'];
}
}
But my issue is I get this thrown:
TypeError: Super expression must either be null or a function
And the stack trace seems to point to when I try to extend AssetWidget from AutocompleteWidget.
My guess is: modern React-Redux does not play well with class-based inheritance in React components or something with inheriting components that are connected to the redux store/state. Or I am forgetting something technical or conceptual when I transferred my company's old React widget code to this new React codebase.
Looking at other posts: I don't seem to have any circular dependency that I see, and my React version is up-to-date.
I hate class-based React and I usually do everything in functions so I could be forgetting something critical.
I have a component in my app that renders some data, most commonly a page title.
Markup:
<Toolbar>
<ToolbarRow>
<div id="title-bar">
{children}
</div>
</ToolbarRow>
</Toolbar>
How would I declaratively be able to change the data inside?
I've tried react-side-effects which allowed me to indeed change the title to be rendered but then I wanted to be able to add components as well.
Components aren't to be stored inside state so there's that…
Then I looked at Portals, which seem to exactly what I want but I get Target container is not a DOM element.
Markup for the portal component:
import React from "react";
import {createPortal} from 'react-dom'
const PageTitle = ({title, children}) => {
return createPortal(
<p>foo</p>,
document.getElementById('title-bar')
)
};
export default PageTitle;
I'm calling the portal component like so:
<PageTitle title="Document Overview"/>
As you can see from the above snippet, the other component adds a <div id="title-bar" />, so I guess it has to do with timing.
Anyone have a good idea?
I would just put components into the state here:
const bars = [];
export class TitleBar extends Component {
state = { children: [] };
componentDidMount() { bars.push(this); }
componentWillUnmount() { bars.splice(bars.indexOf(this), 1); }
render() { return this.state.children };
}
const RealPageTitle = ({ title }) => <div> { title } </div>;
export class PageTitle extends Component {
constructor(props) {
super(props);
this.real = RealPageTitle(props);
}
componentDidMount() {
for(const bar of bars)
bar.setState(({ children }) => ({ children: children.concat(this.real) }));
}
componentWillUnmount() {
for(const bar of bars)
bar.setState(({ children }) => ({ children: children.filter(child => child !== this.real) }));
}
render() { }
}
That way you can just add <PageTitle title={"Test"} /> somewhere on the page and it gets added to the title bar.
I know this does not follow "best practices", but it certainly works
I am following this tutorial: https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/ which i think is the 'standard' way of doing server side rendering in react (?).
Basically what happens is i use react router (v4) to make a tree of all the components that are about to get rendered:
const promises = branch.map(({ route }) => {
return route.component.fetchInitialData
? route.component.fetchInitialData(store.dispatch)
: Promise.resolve();
});
Wait for all those promises to resolve and then call renderToString.
In my components i have a static function called fetchInitialData which looks like this:
class Users extends React.Component {
static fetchInitialData(dispatch) {
return dispatch(getUsers());
}
componentDidMount() {
this.props.getUsers();
}
render() {
...
}
}
export default connect((state) => {
return { users: state.users };
}, (dispatch) => {
return bindActionCreators({ getUsers }, dispatch);
})(Users);
And all this works great except that getUsers is called both on the server and the client.
I could of course check if any users are loaded and not call getUsers in componentDidMount but there must be a better, explicit way to not make the async call twice.
After getting more and more familiar with react i feel fairly confident i have a solution.
I pass a browserContext object along all rendered routes, much like staticContext on the server. In the browserContext i set two values; isFirstRender and usingDevServer. isFirstRender is only true while the app is rendered for the first time and usingDevServer is only true when using the webpack-dev-server.
const store = createStore(reducers, initialReduxState, middleware);
The entry file for the browser side:
const browserContext = {
isFirstRender: true,
usingDevServer: !!process.env.USING_DEV_SERVER
};
const BrowserApp = () => {
return (
<Provider store={store}>
<BrowserRouter>
{renderRoutes(routes, { store, browserContext })}
</BrowserRouter>
</Provider>
);
};
hydrate(
<BrowserApp />,
document.getElementById('root')
);
browserContext.isFirstRender = false;
USING_DEV_SERVER is defined in the webpack config file using webpack.DefinePlugin
Then i wrote a HOC component that uses this information to fetch initial data only in situations where it is needed:
function wrapInitialDataComponent(Component) {
class InitialDatacomponent extends React.Component {
componentDidMount() {
const { store, browserContext, match } = this.props;
const fetchRequired = browserContext.usingDevServer || !browserContext.isFirstRender;
if (fetchRequired && Component.fetchInitialData) {
Component.fetchInitialData(store.dispatch, match);
}
}
render() {
return <Component {...this.props} />;
}
}
// Copy any static methods.
hoistNonReactStatics(InitialDatacomponent, Component);
// Set display name for debugging.
InitialDatacomponent.displayName = `InitialDatacomponent(${getDisplayName(Component)})`;
return InitialDatacomponent;
}
And then the last thing to do is wrap any components rendered with react router with this HOC component. I did this by simply iterating over the routes recursively:
function wrapRoutes(routes) {
routes.forEach((route) => {
route.component = wrapInitialDataComponent(route.component);
if (route.routes) {
wrapRoutes(route.routes);
}
});
}
const routes = [ ... ];
wrapRoutes(routes);
And that seems to do the trick :)
I want to make simple CRUD functionality which should be the same in my web app, Android app and iOS app.
Therefore, I have in node.js made an API from which I can retrieve a paginated list of all documents, a detail page of each document, and post/put requests to delete and update.
Now I am able to check for administrator rights and other things at these routes. Is this the correct approach?
Now I want to traverse this paginated list and present it with reactjs.
I guess it could be done with
import React from 'react';
import ReactDOM from 'react-dom';
export default class ItemLister extends React.Component {
constructor() {
super();
this.state = { items: [] };
console.log('test');
}
componentDidMount() {
fetch('/api/events/').then(result => {
this.setState({items:result.json()});
});
}
render() {
return (
<div>
<div>Items:</div>
{ this.state.items.map(item => { return <div>{http://item.name}</div>}) }
</div>
);
}
}
ReactDOM.render(<ItemLister/>, document.getElementById('hello-world'));
but nothing happens (it doesn't even print anything in the console window as i tried in the constructor).
I have this script in a file /components/ItemLister.jsx and I have imported react-15.2.1 and react-dom-15.2.1. I have installed babel-cli, babel-preset-es2015, babel-preset-react and set
"presets": [
"es2015",
"react"
]
in my package.json.
Issue with {http://item.name}
Put :
{ this.state.items.map(item => <div>http://{item.name}</div>) }
NOT :
{ this.state.items.map(item => { return <div>{http://item.name}</div>}) }
Here with Sample code using SWAPI and showing results.
Full code: https://react-vzbqum.stackblitz.io
import React from 'react';
import ReactDOM from 'react-dom';
export default class ItemLister extends React.Component {
constructor() {
super();
this.state = { items: [] };
console.log('test');
}
componentDidMount() {
fetch('https://swapi.co/api/planets').then(res => res.json()).then(res => {
console.log(res.results);
if (res.results && res.results.length > 0) {
this.setState({items: res.results});
}
});
}
render() {
return (
<div>
<div>Items:</div>
{ this.state.items.map(item => { return <div>{`http://${item.name}`}</div>}) }
</div>
);
}
}
ReactDOM.render(<ItemLister/>, document.getElementById('hello-world'));
Your issue is at below line.
{ this.state.items.map(item => { return <div>{http://item.name}</div>}) }
As you using ES6 you no need to use return when returning on the same line. Replace the above code with below one...
{ this.state.items.map((item,index) => <div key={item.index}>http://{item.name}</div>)}
React recommends using key attribute with unique key value for better performance reconciliation when we insert dynamic nodes using map.
I'm having trouble figuring out how to short circuit rendering a branch
of a tree of React components using Immutable.js cursors.
Take the following example:
import React from 'react';
import Immutable from 'immutable';
import Cursor from 'immutable/contrib/cursor';
let data = Immutable.fromJS({
things: [
{title: '', key: 1},
{title: '', key: 2}
]
});
class Thing extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.thing.deref() !== nextProps.thing.deref();
}
handleChangeTitle(e) {
this.props.thing.set('title', e.target.value);
}
render() {
return <div>
<input value={this.props.thing.get('title')}
onChange={this.handleChangeTitle.bind(this)} />
</div>;
}
}
class Container extends React.Component {
render() {
const cursor = Cursor.from(this.props.data, 'things', newThings => {
data.set('things', newThings);
renderContainer();
});
const things = cursor.map(thing => (
<Thing thing={thing} key={thing.get('key')} />
));
return <div>
{things}
</div>;
}
}
const renderContainer = () => {
React.render(<Container data={data} />, document.getElementById('someDiv'));
};
Say I change the first Thing's title. Only the first Thing will render with
the new title and the second Thing will not re-render due to
shouldComponentUpdate. However, if I change the second Thing's title, the
first Thing's title will go back to '' since the second Thing's cursor
is still pointing at an older version of the root data.
We update the cursors on each render of Container but the ones that don't
render due to shouldComponentUpdate also don't get the new cursor with the updated
root data. The only way I can see keeping the cursors up to date is to remove
shouldComponentUpdate in the Thing component in this example.
Is there a way to change this example to use shouldComponentUpdate using fast referential
equality checks but also keep the cursors updated?
Or, if that's not possible, could you provide an overview of how you would generally work with cursors + React components and rendering only components with updated data?
I updated your code, see comments inline:
class Thing extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.thing.deref() !== nextProps.thing.deref();
}
handleChangeTitle(e) {
// trigger method on Container to handle update
this.props.onTitleChange(this.props.thing.get('key'), e.target.value);
}
render() {
return <div>
<input value={this.props.thing.get('title')}
onChange={this.handleChangeTitle.bind(this)} />
</div>;
}
}
class Container extends React.Component {
constructor() {
super();
this.initCursor();
}
initCursor() {
// store cursor as instance variable to get access from methods
this.cursor = Cursor.from(data, 'things', newThings => {
data = data.set('things', newThings);
// trigger re-render
this.forceUpdate();
});
}
render() {
const things = this.cursor.map(thing => (
<Thing thing={thing} key={thing.get('key')} onTitleChange={this.onTitleChange.bind(this)} />
));
return <div>
{things}
</div>;
}
onTitleChange(key, title){
// update cursor to store changed things
this.cursor = this.cursor.update(x => {
// update single thing
var thing = x.get(key - 1).set('title', title);
// return updated things
return x.set(key - 1,thing);
});
}
}
const renderContainer = () => {
React.render(<Container data={data} />, document.getElementById('someDiv'));
};