React Native + React (for web) seed project - reactjs

Is it possible to setup a project which has code for both React Native(Mobile app) + React(web), having the code shred between platforms except for the UI part.
Have done something similar with Angular + NativeScript using this seed, which enables code sharing between native app and web application(Except for the UI layer). Looking for something similar for React + React Native.
Please share if you know any such seed for React Native + Angular as well, if available.

Jonathan Kaufman has a good article on how to set this up: http://jkaufman.io/react-web-native-codesharing/
The basic strategy is to have a different entry point (index.js) for each platform (android/ios/web). Then the majority of your non-rendering code can live in a shared app or common folder. You'll still need to segregate your rendering code (i.e. uses of View, div, etc.), though, as that will differ by platform.
Pay attention to the comments on that article as well, as there's some good discussion on the pitfalls of this approach. Example:
By sharing a common package.json between native and web, you've glued them together by their common dependencies, the most important one being react. Let's say you upgrade to a version of react-native that depends on >= react#16, but your web app depends on some other library which depends on =< react#15. --timtas

You can give a try to React-Native-Web, but imho you should create 2 different projects, isolate and copy what can be used on both (like api requests and util functions). Your code will be easier to debug and maintain.

Yes, absolutely possible. We've done it before using this lib react-native-web. https://github.com/necolas/react-native-web
beside index.ios.js and index.android.js, you will need create index.web.js, the content should be similar like this.
import { AppRegistry } from 'react-native';
import App from './app/Containers/App/App.container';
AppRegistry.registerComponent('ReactNativeWeb', () => App);
AppRegistry.runApplication('ReactNativeWeb', { rootTag: document.getElementById('react-app') });
also you need to create your own nodejs code to serve up the bundle. full reference

I do it this way.
1) Create a React Native project.
2) Add react-dom and react-scripts dependencies to package.json and install them.
3) All the component code is separated this way:
My regular React Native component:
// MyComponent.js
class MyComponent extends React.Component {
costructor(props) {
...
}
someMethod() {
...
}
render() {
return (
<View>
...
</View>
)
}
}
Changed for using in web:
// MyComponentController.js
class MyComponentController extends React.Component {
costructor(props) {
...
}
someMethod() {
...
}
}
// MyComponent.js
const MyComponentController = require('./MyComponentController')
class MyComponent extends MyComponentController {
render() {
return (
<div>
...
</div>
)
}
}
// MyComponent.native.js
const MyComponentController = require('./MyComponentController')
class MyComponent extends MyComponentController {
render() {
return (
<View>
...
</View>
)
}
}
And then I use in it in all the platforms:
const MyComponent = require('./MyComponent')
For this to work nicely with an old project I had to implement some dummies, but it all can be done better by your own layer of abstraction. Part of my example:
const ReactNative = {
Platform: {
OS: 'web'
},
AppRegistry: {
registerComponent: (name, provider) => {
const Component = provider()
ReactDOM.render(
<Component />,
document.getElementById('root')
);
}
},
AsyncStorage: {
setItem: (key, value, callback) => {
localStorage.setItem(key, value)
callback()
},
getItem: key => {
const val = localStorage.getItem(key) || null
return new Promise(ok => ok(val))
}
},
StyleSheet: {
create: dict => dict
},
Dimensions: {
get: function() {
// http://stackoverflow.com/questions/3437786/get-the-size-of-the-screen-current-web-page-and-browser-window
const w = window
const d = document
const e = d.documentElement
const g = d.getElementsByTagName('body')[0]
const x = w.innerWidth || e.clientWidth || g.clientWidth
const y = w.innerHeight|| e.clientHeight|| g.clientHeight
return {
width: x,
height: y
}
}
},
Linking: {
openURL: (url) => {
window.open(url)
}
},
// etc, I add dummies as soon as I need them
}
But, as I said, this was necessary only because I did not have much time and had not known in advance that I would have to port to web.

You can try the repo that I tried to prepare:
https://mehmetkaplan.github.io/react-spa-jwt-authentication-boilerplate/
This has a step by step guideline that enables to share common logic between react and react-native applications.
It aims to differentiate only in the presentation layer. Other than that all logic is compiled to be shared between applications.
It also comes with facebook and google logins, database (mysql) integration, WebView task generation, etc.
And also it gives the fundamental know-how on "single page applications", "JWT (json web token) based security", etc..
Once read the README, you can simply clone the repo and set your environment (Database) and start developing business logic on top of the shared code structure and security baseline.

You can create 3 stand-alone applications - React, React-native & Server.
Both React & React-native will use the same services from your back-end app.
Else go with a single app where on loading home page, app will understand the device and render React / React-native code as per the device.

Related

How to show build datetime on my react web app using create-react-app?

tl;dr version - I want to show the date of deployment of the production application on the footer of my application so that the users know exactly when the application was last updated.
I want it to be automatic, but I can't think of a way to capture the current date time during build and then parse it into html (jsx) to show them.
Update - Btw, I use CRA and have no plans of ejecting from it! At least not for this.
Context - Why do I want this?
I have a production application in React that I don't have unrestricted access to. There were some issues raised on there and I was able to replicate it on my dev and I fixed the issues and deployed it (or I thought I did). But the issue remained on production and after more time debugging, I just asked for a lot of diagnostic information from them to check if there was something wrong with the production API or database etc. But it finally turned out that my deployment to production wasn't successful. I think that if I showed the last deployement date-time somewhere on the app, I could just ask that from the users rather than get into elaborate diagnostic information.
I'm a Create React App maintainer!
Starting in Create React App 2 (react-scripts#^2.0) you can accomplish this via macros.
First, install preval.macro:
$ npm install --save preval.macro # if using npm
$ yarn add preval.macro # if using yarn
Next, in the file you want to render a build timestamp in, include preval.macro:
import preval from 'preval.macro'
Finally, you can create a constant and use it in your app like so:
const dateTimeStamp = preval`module.exports = new Date().toLocaleString();`
Here's a full example:
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
import preval from 'preval.macro'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Build Date: {preval`module.exports = new Date().toLocaleString();`}.
</p>
</header>
</div>
)
}
}
export default App
The solution from joe-haddad is working well. Here is a drop-in component you can use like <VersionNumber />. This will display something like:
import React from 'react';
import moment from 'moment';
import packageJson from '../../../package.json';
import preval from 'preval.macro';
const buildTimestamp = preval`module.exports = new Date().getTime();`;
class Component extends React.Component {
getDateString = () => {
const lastUpdateMoment = moment.unix(buildTimestamp / 1000);
const formattedDate = lastUpdateMoment.format('DD.MM.YYYY HH:mm:ss');
return formattedDate;
};
render () {
return (
<div>
{packageJson.version}
{'.'}
{buildTimestamp}
{' '}
{'('}
{this.getDateString()}
{')'}
</div>
);
}
}
Component.propTypes = {};
Component.defaultProps = {};
export default Component;
Set a process environment using webpack. You can save the value to a local json file and read from it with an increment during the compilation.
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key];
return env;
},
{
// Useful for determining whether we’re running in production mode.
// Most importantly, it switches React into the correct mode.
NODE_ENV: process.env.NODE_ENV || 'development',
REACT_APP_DATE_TIME: new Date().toDateString(),
// Useful for resolving the correct path to static assets in `public`.
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
// This should only be used as an escape hatch. Normally you would put
// images into the `src` and `import` them in code to get their paths.
PUBLIC_URL: publicUrl,
}
);
// Stringify all values so we can feed into Webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key]);
return env;
}, {}),
};
return { raw, stringified };
}
module.exports = getClientEnvironment;
The old solution will also work. Please check the code again. As i can see the proper value in my app.
A contribution to Joe Haddat's answer; if you want to illustrate the build date by using preval.macro in the viewer timezone instead of the environment timezone which builds the app, here is how to do it;
render() {
const buildDate = preval`module.exports = new Date();`
return (
<div>Build Date: {new Date(buildDate).toLocaleString()}</div>
)
}
This will save the build date, but turning it into a locale string will still happen on runtime, on the browser where the viewer timezone is used.

react native (expo) load markdown files

I'm having some troubles loading markdown files (.md) into my react native (non-detached expo project).
Found this awesome package that allows me to render it. But can't figure out how to load the local .md file as a string.
import react from 'react';
import {PureComponent} from 'react-native';
import Markdown from 'react-native-markdown-renderer';
const copy = `# h1 Heading 8-)
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
`;
export default class Page extends PureComponent {
static propTypes = {};
static defaultProps = {};
render() {
return (
<Markdown>{copy}</Markdown>
);
}
}
BTW: I tried googling, but can't get the suggestions to work
https://forums.expo.io/t/loading-non-media-assets-markdown/522/2?u=norfeldtconsulting
I tried the suggested answers for reactjs on SO, but the problem seems to be that it only accepts .js and .json files
Thanks to #Filipe's response, I got some guidance and got a working example that will fit your needs.
In my case, I had a .md file on the assets/markdown/ folder, the file is called test-1.md
The trick is to get a local url for the file, and then use the fetch API to get its content as a string.
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Markdown from 'react-native-markdown-renderer';
const copy = `# h1 Heading 8-)
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
`;
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
copy: copy
}
}
componentDidMount() {
this.fetchLocalFile();
}
fetchLocalFile = async () => {
let file = Expo.Asset.fromModule(require("./assets/markdown/test-1.md"))
await file.downloadAsync() // Optional, saves file into cache
file = await fetch(file.uri)
file = await file.text()
this.setState({copy: file});
}
render() {
return (
<Markdown>{this.state.copy}</Markdown>
);
}
}
EDIT: In order to get get rid of the error
Unable to resolve "./assets/markdown/test-1.md" from "App.js"
you would need to add the packagerOpts part of #Filipe's snippet into your app.json file.
app.json
{
"expo": {
...
"assetBundlePatterns": [
"**/*"
],
"packagerOpts": {
"assetExts": ["md"]
},
...
}
}
EDIT 2:
Answering to #Norfeldt's comment:
Although I use react-native init when working on my own projects, and I'm therefore not very familiar with Expo, I got this Expo Snack that might have some answers for you: https://snack.expo.io/Hk8Ghxoqm.
It won't work on the expo snack because of the issues reading non-JSON files, but you can test it locally if you wish.
Using file.downloadAsync() will prevent the app making XHR calls to a server where your file is hosted within that app session (as long as the user does not close and re-open the app).
If you change the file or modify the file (simulated with a call to Expo.FileSystem.writeAsStringAsync()), it should display the updated as long as your component re-renders and re-downloads the file.
This will happen every time your app is closed and re-open, as the file.localUri is not persisted per sessions as far as I'm concerned, so your app will always call file.downloadAsync() at least once every time it's opened. So you should have no problems displaying an updated file.
I also took some time to test the speed of using fetch versus using Expo.FileSystem.readAsStringAsync(), and they were on average the same. Often times Expo.FileSystem.readAsStringAsync was ~200 ms faster, but it 's not a deal breaker in my opinion.
I created three different methods for fetching the same file.
export default class MarkdownRenderer extends React.Component {
constructor(props) {
super(props)
this.state = {
copy: ""
}
}
componentDidMount() {
this.fetch()
}
fetch = () => {
if (this.state.copy) {
// Clear current state, then refetch data
this.setState({copy: ""}, this.fetch)
return;
}
let asset = Expo.Asset.fromModule(md)
const id = Math.floor(Math.random() * 100) % 40;
console.log(`[${id}] Started fetching data`, asset.localUri)
let start = new Date(), end;
const save = (res) => {
this.setState({copy: res})
let end = new Date();
console.info(`[${id}] Completed fetching data in ${(end - start) / 1000} seconds`)
}
// Using Expo.FileSystem.readAsStringAsync.
// Makes it a single asynchronous call, but must always use localUri
// Therefore, downloadAsync is required
let method1 = () => {
if (!asset.localUri) {
asset.downloadAsync().then(()=>{
Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
})
} else {
Expo.FileSystem.readAsStringAsync(asset.localUri).then(save)
}
}
// Use fetch ensuring the usage of a localUri
let method2 = () => {
if (!asset.localUri) {
asset.downloadAsync().then(()=>{
fetch(asset.localUri).then(res => res.text()).then(save)
})
} else {
fetch(asset.localUri).then(res => res.text()).then(save)
}
}
// Use fetch but using `asset.uri` (not the local file)
let method3 = () => {
fetch(asset.uri).then(res => res.text()).then(save)
}
// method1()
// method2()
method3()
}
changeText = () => {
let asset = Expo.Asset.fromModule(md)
Expo.FileSystem.writeAsStringAsync(asset.localUri, "Hello World");
}
render() {
return (
<ScrollView style={{maxHeight: "90%"}}>
<Button onPress={this.fetch} title="Refetch"/>
<Button onPress={this.changeText} title="Change Text"/>
<Markdown>{this.state.copy}</Markdown>
</ScrollView>
);
}
}
Just alternate between the three to see the difference in the logs.
From what I know, this cannot be done within expo. I use react-native and run it on my mobile for development.
react-native use Metro as the default bundler, which also suffers from similar problems. You have to use haul bundler instead.
npm install --save-dev haul
npx haul init
npx haul start --platform android
In a seperate terminal run react-native run-android. This would use haul instead of metro to bundle the files.
To add the markdown file, install raw-loader and edit the haul.config.js file. raw-loader imports any file as a string.
Customise your haul.config.js to look something like this:
import { createWebpackConfig } from "haul";
export default {
webpack: env => {
const config = createWebpackConfig({
entry: './index.js',
})(env);
config.module.rules.push({
test: /\.md$/,
use: 'raw-loader'
})
return config;
}
};
Now you can import the markdown file by using const example = require('./example.md')
Haul supports webpack configuration so you can add any custom babel transform you want.
I don't know exactly where the problem lies, but I added html files to the project, and I'd imagine it would be very similar.
Inside your app.json, try adding these fields:
"assetBundlePatterns": [
"assets/**",
],
"packagerOpts": {
"assetExts": ["md"]
},
The packagerOpts makes it so the standalone will bundle the .md files. I'd imagine you already have an assets folder, but just in case you don't, you will need one.
Then, on AppLoading, loading the assets with Asset.loadAsync might not be needed, but it's a good idea to rule out. Check out the documentation on how to use it.
When importing the file, there are three ways you might want to do so, that change depending on the environment. I'll copy this excerpt from my Medium article:
In the simulator, you can access any file in the project. Thus, source={require(./pathToFile.html)} works. However, when you build a standalone, it doesn’t work quite in the same way. I mean, at least for android it doesn’t. The android webView doesn’t recognise asset:/// uris for some reason. You have to get the file:/// path. Thankfully, that is very easy. The assets are bundled inside file:///android_asset (Careful, don’t write assets), and Expo.Asset.fromModule(require(‘./pathToFile.html')).localUri returns asset:///nameOfFile.html. But that’s not all. For the first few times, this uri will be correct. However, after a while, it changes into another file scheme, and can’t be accessed in the same way. Instead, you’ll have to access the localUri directly. Thus, the complete solution is:
/* Outside of return */
const { localUri } = Expo.Asset.fromModule(require('./pathToFile.html'));
/* On the webView */
source={
Platform.OS === ‘android’
? {
uri: localUri.includes('ExponentAsset')
? localUri
: ‘file:///android_asset/’ + localUri.substr(9),
}
: require(‘./pathToFile.html’)
}
(A constant part of the uri is ExponentAsset, that’s why I chose to check if that was part of it)
That should probably solve your problem. If it doesn't, comment what's going wrong and I'll try to help you further. Cheers!
If you want to load .md file with react-native cli (without expo). I've got a solution for you)
Add https://github.com/feats/babel-plugin-inline-import to your project
Add config .babelrc file with code inside:
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
[
"inline-import",
{
"extensions": [".md", ".txt"]
}
],
[
"module-resolver",
{
"root": ["./src"],
"alias": {}
}
]
]
}
Add to your metro.config.js such code
const metroDefault = require('metro-config/src/defaults/defaults.js');
...
resolver: {
sourceExts: metroDefault.sourceExts.concat(['md', 'txt']),
}
....
Reload your app

React application with external plugins

I'm building a React application bundled using Parcel or Webpack.
The application should be able to embed external React components
developed by third-parties and hosted elsewhere as modern javascript modules:
// https://example.com/scripts/hello-plugin.js
import React from 'react';
export default class HelloPlugin extends React.Component {
render() {
return "Hello from external plugin!";
}
}
Host application loads these components using asynchronous import like this, for example:
// createAsyncComponent.tsx
import * as React from 'react';
import { asyncComponent } from 'react-async-component';
export default function createAsyncComponent(url: string) {
return asyncComponent({
resolve: () => import(url).then(component => component.default),
LoadingComponent: () => <div>Loading {url}....</div>,
ErrorComponent: ({ error }) => <div>Couldn't load {url}: {error.message}</div>,
})
}
But looks like bundlers don't allow importing arbitrary urls as external javascript modules.
Webpack emits build warnings: "the request of a dependency is an expression" and the import doesn't work. Parcel doesn't report any errors, but fails when import(url) occurs at runtime.
Webpack author recommends using scriptjs or little-loader for loading external scripts.
There is a working sample that loads an UMD component from arbitrary URL like this:
public componentDidMount() {
// expose dependencies as globals
window["React"] = React;
window["PropTypes"] = PropTypes;
// async load of remote UMD component
$script(this.props.url, () => {
const target = window[this.props.name];
if (target) {
this.setState({
Component: target,
error: null,
})
} else {
this.setState({
Component: null,
error: `Cannot load component at ${this.props.url}`,
})
}
});
}
Also, I saw a similar question answered a year ago where the suggested approach also involves passing variables via a window object.
But I'd like to avoid using globals given that most modern browsers support modules out of the box.
I'm wondering if it's possible. Perhaps, any way to instruct the bundler that my import(url) is not a request for the code-split chunk of a host application, but a request for loading an external Javascript module.
In the context of Webpack, you could do something like this:
import(/* webpackIgnore: true */'https://any.url/file.js')
.then((response) => {
response.main({ /* stuff from app plugins need... */ });
});
Then your plugin file would have something like...
const main = (args) => console.log('The plugin was started.');
export { main };
export default main;
Notice you can send stuff from your app's runtime to the plugin at the initialization (i.e. when invoking main at the plugin) of the plugins so you don't end up depending on global variables.
You get caching for free as Webpack remembers (caches) that the given URL has already loaded so subsequent calls to import that URL will resolve immediately.
Note: this seems to work in Chrome, Safari & firefox but not Edge. I never bothered testing in IE or other browsers.
I've tried doing this same sort of load with UMD format on the plugin side and that doesn't seem to work with the way Webpack loads stuff. In fact it's interesting that variables declared as globals, don't end up in the window object of your runtime. You'd have to explicitly do window.aGlobalValue = ... to get something on the global scope.
Obviously you could also use requirejs - or similar - in your app and then just have your plugins follow that API.
Listen to the Webpack author. You can't do (yet) what you're trying to do with Webpack.
You will have to follow his suggested route.

Sonarqube : Create a custom page using react

I would like to create a custom page using react but I cannot find the documentation to do this. On the Sonarqube documentation, there only the way to create a custom page using javascript only and I don’t understand how the example plugin works with react.
Can you tell me if there is a documentation that I can use.
Short answer: There isn't. There is barely anyone (no one in fact, as far as I've seen) using custom pages currently.
However, it IS possible. You need to create a react project with Webpack (or a similar JS packager).
I also recommend using Create-React-App. This fixes a lot of the setup for you. After that, in your index.js you use the example code from the SonarQube wiki.
Here is an example:
/*
PRODUCTION ENTRYPOINT
*/
import React from 'react';
import ReactDOM from 'react-dom';
import Project from './components/Project';
import './main.css';
window.registerExtension('myplugin/coverage', function (options) {
appendCustomCSS();
let isDisplayed = true;
window.SonarRequest.getJSON('/api/measures/component', {
component: options.component.key,
metricKeys: 'coverage'
}).then(function (response) {
if (isDisplayed) {
let obj = JSON.parse(response.component.measures[0].value);
let div = document.createElement('div');
render(obj, div);
options.el.appendChild(div);
}
});
return function () {
isDisplayed = false;
};
});
function appendCustomCSS() {
let fileref = document.createElement("link");
fileref.setAttribute("rel", "stylesheet");
fileref.setAttribute("type", "text/css");
fileref.setAttribute("href", "/static/myplugin/coverage.css");
document.head.append(fileref);
}
function render(objectArray, container) {
ReactDOM.render(<div className="Coverage"><Project objects={objectArray}/></div>, container);
}

How to write Autodesk.Viewing.Extensions with webpack and react?

I was write code within view3D v2.11, React, ES6 and webpack. But I don't know how to write Autodesk.Viewing.Extensions within webpack and React. Can anyone show me some examples?
Using webpack to write a viewer extension is no different than using webpack to write any other js application. Take a look at my extensions library repo, each extension is bundled into a separate .js or .min.js whether you build the project in dev or prod: library-javascript-viewer-extensions.
This is designed this way so each extension can be loaded dynamically independently, however if you build an entire application around the viewer, you may simply include the code for each extension along with the rest of the app and generate a single webpack bundle.
This React project contains multiple viewer extensions (some are extracted from the library mentioned above) and is bundling extensions code along with the rest of the app: forge-rcdb.
As far as React and the viewer are concerned, it's not very relevant because the viewer is a 3D component which is created dynamically, all WebGL canvas and viewer 2D elements are being generated after your app loads a model, whereas React lets you declare 2D components when the app is starting. I was playing a bit with injecting dynamically React components to the viewer div, you can take a look in that same project here:
this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(this.viewerContainer)
// Experimental !
// This this to render dynamic components
// inserted after the viewer div has been created
const reactViewerNode = document.createElement('div')
this.viewer.container.appendChild(reactViewerNode)
this.viewer.react = {
node: reactViewerNode,
components: [],
addComponent: (component) => {
this.viewer.react.components.push(component)
},
render: (props) => {
ReactDOM.render(
<div>
{
React.Children.map(
this.viewer.react.components,
(child) => React.cloneElement(child, props))
}
</div>,
reactViewerNode)
}
}
I then render those dynamic components by overriding componentDidUpdate:
componentDidUpdate () {
if (this.viewer && this.viewer.react) {
if(this.viewer.react.components.length) {
this.viewer.react.render(this.props)
}
}
}
and there is an example of use:
viewer.react.addComponent(
<DBDropdown key="test" className="react-div">
</DBDropdown>
)
I actually haven't implemented any feature using that in the live version of the app, but it should give you an idea.
Hope that helps.

Resources