Openwrt 配置端口映射

为了能够通过外网访问并控制刷了Openwrt的路由器,我们需要对端口进行映射,由于运营商封锁了80端口,我们通过将其他端口映射到路由器的80端口中来进行访问,以下为具体步骤。
1.首先配置防火墙,从网络–>防火墙–>一般设置进行配置,必须打开所有的入站数据以及端口转发数据才行,如下图

2.增加端口转发规则,共享名自己随便起,外部区域是WAN,端口port是从外部访问的端口,自己设别设80被屏蔽就行,内部为LAN,ip就是路由器在内网中的ip,端口就是80

然后通过 IP:port 访问就可以了,port就是第二部设置的port,当然用ip访问不方便,推荐使用ddns服务。

FSImage Parse For Summary

很多时候我们想要解析一个FSImage文件分析当时的hdfs,但是我们并不想获得所有的inode与block,而只是想知道summary,比如有多少个INodes,有多少个Blocks,多少个Directory,多少个Files。如果使用OfflineImageViewr会占用大量的内存,很多时候甚至是不现实的,所以开发一个简单的summary程序方便查看状态十分必要。


首先看一下FSImage文件的结构,FSImage文件按照Section进行存储,
1.MAGIN_HEADER (“HDFSIMG1″)
2.FILE_VERSION,LAYOUT_VERSION
3.SECTION
3.1 NS_INFO Section
包含了timestamp,lastAllocateBlockId,txid等等。
3.2 INODES Section
固化所有INode节点,INodeDirectory节点,FileUnderconstruction节点,以及Snapshot数据。
3.3 DELEGATION_TOKENS Section
3.4 CACHE_POOLS Section
4.FileSummary
每一个Section固化结束后都想summary写入相应的Section名称,长度及偏移量,方便读取。

所以读取的时候我们首先读取到summary中信息,根据summary中信息跳转到文件的offset部分即可读取,对于我上面所说的需求,我们感兴趣的是NS_INFO Section和INode Section,其它Section我们不读即可。
1.读取到NS_INFO Section我们去获得timestamp,lastBlockId和TransactionId。
2.读取到INdoes Section,我们只需记录文件个数和目录个数即可。
工程在我的github

browserify和webpack快速配置入门教程

随着前端的工程越来越复杂,快速的模块化构建、部署前端app也就变得更加的重要,最近比较火爆的工具有browserify和webpack。真的是非常好用,本文的目的就是教会大家怎么使用这两个工具,因为强大所以配置也非常复杂,但是我们常用的核心功能非常简单,下面我们就从实战的角度,告诉大家怎么能用其快速的构建应用,本文中的打包代码基本是 即拷贝即用。

browserify

简介

browserify 简单概括来说就是:按照依赖(require)打包你的js文件。并让它(node端代码)跑在浏览器环境下。
浏览器兼容程度如下:

快速使用方法


npm install -g browserify browserify main.js -o bundle.js

假设main.js是你的node模块代码,且main.js依赖了 basicA.js basicB.js。你可以通过上述命令快速的产出bundle.js文件,在浏览器端使用,在bundle.js中会实现如下功能,所以最终代码就直接引用bundle.js即可。

  • 增加对node的require和exports的支持,使得main.js的内容能够在浏览器端执行
  • 分析出main.js的依赖模块basicA.js basicB.js并将其打包在bundle.js中
  • 上述只是最简单的使用方法,详情请参考 browserify-handbook

gruntfile版本react工程最简配置(支持文件修改自动部署)

我们知道react是支持使用node模块和页面内嵌jsx,但是一般来说,react应用还是需要打包的步骤,将jsx的语法解析成对应的js执行。browsify支持react项目的打包,只需要引入对应的reactify 插件即可。最简单的配置如下:

//package.json
{
  "name": "react-app",
  "version": "0.0.1",
  "dependencies": {
    "grunt": "^0.4.5",
    "grunt-browserify": "^3.3.0",
    "grunt-contrib-watch": "^0.6.1",
    "reactify": "^1.0.0"
  }
}
//gruntfile文件配置
//其中所有的jsx组件放到src下,而最终的入口文件为app.js
//开发的时候执行grunt watch就可以监控src中所有jsx模板将其翻译成对应的js模块,在最终的html中引入bundle.js即可
module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      browserify: {
        files: ['src/**/*.js*', 'app.js'],
        tasks: ['browserify']
      }
    },
    browserify: {
      example : {
        src: ['app.js'],
        dest: 'bundle.js',
        options: {
          transform: ['reactify']
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.registerTask('bundle-example', ['browserify:example']);
};  

webpack

简介

webpack 也是一个强大的模块管理工具,它将所有的资源都算作一个模块,如下图。

和前面提到的browsify相比,browsify只是支持js的打包,webpack更加的智能,主要体现:
– 支持CommonJs和AMD模块。
– 支持模块加载器和插件机制,可对模块灵活定制,比如babel-loader加载器,有效支持ES6。
– 可以通过配置,打包成多个文件。有效利用浏览器的缓存功能提升性能。
– 将样式文件和图片等静态资源也可视为模块进行打包。配合loader加载器,可以支持sass,less等CSS预处理器。
– 内置有source map,即使打包在一起依旧方便调试。

快速使用方法

npm install -g webpack
##支持的命令行参数有:-d:支持调试;-w支持实时的编辑打包;-p支持压缩
webpack -d
webpack -w
webpack -p

webpack的默认文件名为:webpack.config.js,下面就介绍一个简单的工程所使用的webpack配置。

//单入口文件的打包
var path = require("path");
module.exports = {
    entry: './src/search.js', //单入口文件
    output: {
        path: path.join(__dirname, 'out'),  //打包输出的路径
        filename: 'bundle.js',              //打包后的名字
        publicPath: "./out/"                //html引用路径,在这里是本地地址。
    }
};
//多入口文件
module.exports = {
    entry: {
        bundle_page1: "./src/search.js",
        bundle_page2: "./src/login.js"
    },
    output: {
        path: path.join(__dirname, 'out'),
        publicPath: "./out/",
        filename: '[name].js'//最后产出的文件为 out/bundle_page1.js out/bundle_page2.js
    }
};

webpack加载器和插件

webpack最大的特色就是支持很多的loader,这些loader为复杂的应用构建提供了便利的部署环境,而不仅仅局限于node文件的浏览器环境打包而已。常用的加载器有哪些呢,这里会介绍这几个的用法。

  • babel-loader:不仅可以做ES6-ES5的loader还可以用来实现jsx的编译
  • less-loader:用于将less编译成对应的css
  • css-loader:加载css文件
  • style-loader:转化成内置的
  • json-loader
  • url-loaderimage sprite 的替代方案,会将制定的图片文件合并加载,有limit参数
  • extract-text-webpack-plugin:项目中如果不采用按需加载,而是要将所有的css打包成一个文件,并使用link加载,这个插件就有所帮助了。

webpack实战一个工程配置

有了上述的loader,我们就可以做很多的项目配置了,假设我们有个实际的项目, 基本的操作都包括如下这些环节,我们该如何使用webpack实现这个功能配置呢?

  • JS编译与加载:loader + react模板开发
  • CSS编译与加载:less编译
  • JS与CSS压缩
//package.json
{
  "name": "order-view",
  "version": "0.0.1",
  "dependencies": {
    "babel-loader": "",
    "less-loader": "",
    "css-loader": "",
    "style-loader": "",
    "autoprefixer-loader": "",
    "extract-text-webpack-plugin":""
  }
}

// webpack.config.js
// 执行webpack -p即可完成压缩
var path = require("path");
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
    //多文件入口
    entry: {
        bundle_page1: "./src/search.js",
        bundle_page2: "./src/login.js"
    },
    //指定文件的输出
    output: {
        path: path.join(__dirname, 'out'),
        publicPath: "./out/",
        filename: '[name].js'
    },
    module: {
        loaders: [
            //处理react模板的配置
            {
                test: /.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                loader: 'babel'
            },
            //生成内置的样式<style>
            //{
            //    test: /.less$/,
            //    exclude: /(node_modules|bower_components)/,
            //    loader: "style!css!less"
            //},
            //将依赖打包成一个css文件
            {
                test: /.less$/,
                exclude: /(node_modules|bower_components)/,
                test: /.less$/,
                loader: ExtractTextPlugin.extract(
                    'css?sourceMap&-minimize!' + 'autoprefixer-loader!' + 'less?sourceMap'
                )

            },
            //图片自动合并加载
            {
                test: /.(jpg|png)$/,
                loader: "url?limit=8192"
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin('[name].css')
    ]
};

YARN Container cleanup kill其它进程导致的NodeManager 挂起


一、现象

在我最近的升级过程中,经常发现一些NodeManager无关挂起,并且挂起前没有任何日志,查看dmesg,也没有任何异常。对于这种情况,非常难查原因,经过同事排查,最后确定是由于Yarn Container的cleanup导致的bug。


二、原因及解决方法

这个问题的jira号是YARN-3678,这个问题产生的原因是当container执行结束后会通过状态机执行cleanup的操作,实现的类是ContainersLauncher.java。cleanup的逻辑如下图:
Alt
1. 首先kill SIGTERM pid,让container能够优雅的退出
2. 随后kill SIGKILL pid,直接kill -9
3. 这时候可能会产生一些问题,如果在这250ms之内这个container已经退出,同时这个pid被分配给其它线程使用了,这时候kill掉新启动的线程,如果是同一个用户启动的话就可能kill掉该线程对应的整个进程。
说的极端一点,如果kill的是一个NodeManager新启动的线程,就会造成NodeManager挂起,这就是产生的原因。
但是这个现象产生需要一定的条件,对于Linux Container Executor,如果使用不同的用户去启动,那么即使kill掉这个pid,也不会被杀。对于Default Container Executor,则会出现这一问题。
为什么没有使用不同用户启动container的原因是你需要将所有用户的账号同步的集群中的所有机器中,这对于我们是不现实的。
为此,我们需要修改代码,修改方法也很简单,在kill -9之前ps一下这个进程的pid,查看一下是否是之前执行的containerId,就可以了,具体代码在github

Using ssmtp to send gmail on linux server

using ssmtp to send gmail on linux server


Sometimes, we want to send email on linux server to alert some event for purpose. We can fake the sender’s email address, and send. But unfortunately, most of the email server will treat these emails as spam, that is not very convenient. So we want to use our username and password to send email through gmail. Here is some step to configure and use ssmpt to do it.


My linux distribution is centos , it is okay if you use ubuntu, just use apt instead yum.

1. #yum install ssmtp
2. #vi /etc/ssmtp/ssmtp.conf     //edit configuration
 
Here is the setting you should add  
AuthUser=YOURNAME@gmail.com
AuthPass=YOURPASSWORD
FromLineOverride=YES
mailhub=smtp.gmail.com:587
UseSTARTTLS=YES
Hostname=gmail.com
TLS_CA_File=/etc/pki/tls/certs/ca-bundle.crt

Beware you have to add TLS_CA_File in the setting, if not ,you will get Cannot open smtp.gmail.com:587 Error.
After that, you can test your setting,
echo “test” | ssmtp -vvv TESTEMAIL@ADDRESS
If everything goes well, congratulations, you success. If not, check /var/log/maillog, i think most of the error is “Authorization failed (534 5.7.14 https://support.google.com/mail/answer/78754 uy4sm4234351pbc.69 – gsmtp)”.
The problem is caused by google security policy. You can resolve it as the following
1.Google will send you a email to remainder you a Sign-in attempt prevented event, login your google account, and permit the login from your server
2.then go to this https://www.google.com/settings/security/lesssecureapps and set “Access for less secure apps” to ON
You can test it using the command mentioned before. If you still can not send email, check /var/log/maillog and google the answer yourself.

短链服务

       发布一个简单的短链服务,也可用于加密。使用方法,dj1211.com:8899/?word=www.youtube.com    word后面参数是链接,随后返回一个链接 www.dj1211.com:8899/88r ,发给别人,自动跳转。

React生命周期、API和深入用法

React火了很久了,一直都停留在照葫芦画瓢按照example凑数的基础上,但是如果真的要了解一个框架,它的生命周期和核心API是最重要的部分了,下面我们就来聊聊React的生命周期、核心API的用法以及React工具集,在使用React进行深度开发的时候,一定会事半功倍。本文假设你已经了解了React的基本开发知识,如果不了解,强烈推荐阮老师的这篇《React 入门实例教程》

一、生命周期

1、创建一个类和实例化的基础方法

 //创建组件类
var ComponentBox = React.createClass({
    //other lifecycle method
    render:  function () {
        //return JSX code
    }
});
//实例化组件
var compInstance = React.render(
      <ComponentBox />,
     document.getElementById('content') //DOM Element
);

2、了解this.state和this.prop

使用过React的人都会知道它有两个属性,state还有props,他们两者都可以作为render中的data输入源,那两者的区别、联系以及正确的用法又是怎么样的呢。参见这篇《props-vs-state》

  • prop是组件的配置项,是可选的,当组件接收这个参数后它就是不变的。组件改变不了它的props,父组件的props还担当着收集子组件配置项的功能。
  • state,在组件初始化的时候会赋予state初始的状态,当组件的状态发生变化的时候,组件内部自行管理着state的变化,所以state可以说是组件的私有值。state参数是可有可无的,但当你的组件是“Stateful Component”的时候你就应该考虑使用state了。

两者在组件变化的时候的状态改变如下:

- props state
Can get initial value from parent Component? Yes Yes
Can be changed by parent Component? Yes No
Can set default values inside Component?* Yes Yes
Can change inside Component? No Yes
Can set initial value for child Components? Yes Yes
Can change in child Components? Yes No

那么两者如何合理的使用和规划呢。举例来说,在官网《thinking-in-react》有这样的一个例子,组件如下图,当需要有过滤和搜索功能的时候,其实组件就是一个“具有状态的组件”了,需要有state管理对应的状态。通过分析,我们可以得到对应的变量规划:

  • 用于表示所有产品列表的 products
  • 用于表示过滤后的列表 (filterProducts)
  • 表示是否使用过滤的功能(checkValue)
  • 使用搜索过滤的关键词(searchValue)

要分析变量是否是state,主要考虑这几个问题:

  • 变量是否会通过父组件传递参数,如果是,那么它肯定不是state,这里products 肯定不是state
  • 变量是否会出现变化,如果不会出现变化,那么它肯定不是state
  • 你会不会根其他的state或者props计算这个值,如果是经过计算得到的,那么它一定不是state,由此可见filterProducts也不是state

再考虑 checkValue和 searchValue在本质上是随组件内部改变的,所以,这两个变量应该作为state管理。所以最后的程序如下

See the Pen React filter list demo by zhangmeng (@zhangmeng712) on CodePen.

3、生命周期详情分析

1) 初始化阶段
  • - getDefaultProps
  • - getInitialState
  • - propTypes
  • - Mixins
  • - statics
  • - displayName

getDefaultProps初始化参数使用,当组件类被创建的时候会被调用一次。输入为函数类型,返回object为this.props的初始化值,当父组件没有指定具体参数的时候,参数会在这个方法中被详细映射,此外 为了增加程序的可读性和容错性,建议在这个方法中指定好参数的初始值。

/*Input.jsx*/
/*Form是input的父组件*/
module.exports = React.createClass({
    displayName: 'Input',
    getDefaultProps: function () {
    return {
      //父组件中具体的参数映射,为了程序更加清晰,建议所有的props都在getDefaultProps中定义
      model: {
        name: '',
        type: '',
        value: '',
        error: false,
        enabled: true
      }
    };
  }
});

/* Form.jsx */
module.exports = React.createClass({
    displayName: 'Form',
    render: function () {
        return  (<form>
                <Input model={fieldModel}
                      ref={fieldModel.name}/>
                </form>);      
    }
})

getInitialState初始化state使用,在组件mount之前被调用一次。输入为函数,返回object为this.state的初始化值。

propTypes用于属性的验证使用,输入为object。如果输入的类型和验证中设置的类型不符,在dev环境会给出提示。除了React内置的类型如 React.PropTypes.array,还支持自定义类型,详情见Reusable Components.

var ComponentBox = React.createClass({      
        //默认值设置
        getDefaultProps: function () {
            return {
                initalX: 14,
                initalProp: '11',  //内置校验
                customProp: 'hi' //自定义校验
            }
        },
        //允许校验属性的方法,只在dev环境会显示warn
        propTypes: {
            //React内置类型
            initalProp: React.PropTypes.array,
            //自定义类型
            customProp: function (props, propName,componentName) {
                if (props[propName] !== 'hello') {
                    return new Error('Validation failed, customProp value needs to be hello');
                }
            }
        }
    });
    var compInstance = React.render(
            <ComponentBox />,
            document.getElementById('content') //DOM Element
    );

Mixins输入为array类型,用于定义组件间共享的方法,其中的方法可以是生命周期的方法,也可以是自定义方法。不过有几点需要注意:

  • 定义在其内部的方法会优先于类上的方法执行
  • 在Mixins中和类上同时定义render方法会抛出异常(Uncaught Error: Invariant Violation: ReactClassInterface: You are attempting to define `render` on your component more than once)
  • 在Mixins数组中定义同样名称的非生命周期方法也会抛出异常(Uncaught Error: Invariant Violation: ReactClassInterface: You are attempting to define `logFunc` on your component more than once. )
var commonMixins = {
        getDefaultProps: function() {
            return {
                initalY: 20
            }
        }
    };
    var commonMixins1 = {
        //定义2个render定义会抛出异常
        //        render: function () {
        //
        //        }
        // 定义同样名称的非生命周期方法也会抛出异常
        logFunc: function() {
            console.log('Mixins log method')
        }
    };
    var commonMixins2 = {
        logFunc: function() {
            console.log('Mixins log method')
        }
    };
    var ComponentBox = React.createClass({
        mixins: [commonMixins,commonMixins1,commonMixins2]
    });
    var compInstance = React.render( < ComponentBox / > ,
        document.getElementById('content') //DOM Element
    );

statics类上的静态方法,可以在实例化之前被类自己调用。

displayName用于在调试信息中标示组件,JSX会自动的设置它的值

2) Mounting阶段
  • - componentWillMount
  • - componentDidMount

componentWillMount只会被调用一次。运行于初始化之后,render方法之前。当在此函数中调用setState后,render会显示被修改的state内容,注意,尽管state内容被改变了,但是不会再多次调用render。

componentDidMount在render执行之后被调用,这个方法只会被调用一次。在这个方法中,可以通过React.findDOMNode(this)对组件的dom元素进行操作,子组件的componentDidMount方法会优先于父组件的componentDidMount方法被调用。我们会在这个方法中执行ajax请求或者调用timer或者用其他类库进行交互。

...
componentDidMount: function () {
            console.log('-------componentDidMount execute-------')
            var input = $('input[type="text"]');
            input.focus();
            var cityName = this.props.propValue;
            $.get('http://api.openweathermap.org/data/2.5/weather?q=' + cityName , function (data,status) {
                  if (status === 'success') {
                        var weather = data.weather || [];
                        if (weather.length && weather[0]) {
                            this.setProps({
                                weather: weather[0].description
                            });
                        }

                  }
            }.bind(this))

        }
3) 数据更新阶段
  • - componentWillReceiveProps
  • - shouldComponentUpdate
  • - componentWillUpdate
  • - componentDidUpdate

componentWillReceiveProps当新的props参数被发现时,就会调用这个方法,普通的render之后是不调用这个函数的。改变之前的props参数可以通过 this.props获得,新的参数可以通过第一个入参获得。在这个方法中调用setState不会触发额外的render调用。

 componentWillReceiveProps: function (nextProps) {
            console.log('-------componentWillReceiveProps execute-------', 'old weather is this.props.weather:', this.props.weather, 'new props is nextProps.weahter:' + nextProps.weather);
        }

shouldComponentUpdate当props和state变化后被触发,初次render和强制更新的时候此方法不会被调用。当你希望某个props或者state的值改变的时候,不需要render被再次执行,就可以在shouldComponentUpdate中return false来实现,而此时接下来的 componentWillUpdate 和componentDidUpdate也不会执行。默认shouldComponentUpdate会自动返回true,但是你可以通过比较参数的变化来重写这个函数,如下:

shouldComponentUpdate: function (nextProps, nextState) {
            console.log('-------shouldComponentUpdate execute-------')
            var flag = true;
            if (nextProps.weather == 'light rain' ) {
                flag = false;
            }
            return flag;
        }

componentWillUpdate判断完是否能调用render之后,就会执行componentWillUpdate,这个函数是用作render之前发生更新的改变。在这个方法中不能使用setState方法,如果要更新state,在componentWillReceiveProps函数中进行处理。

componentDidUpdate当参数变更完成,render执行完成DOM完全被更新之后会触发,可用于操作新更新的DOM元素,入参为 prevProps和 prevState,以防操作中需要变更前的数据。

4) Unmounting阶段
  • - componentWillUnmount

componentDidUpdate当组件从DOM中销毁的时候调用,可在函数中对timer和不需要的dom元素进行清理

关于组件的生命周期执行顺序和props更新流程等可以参考以下的例子:

See the Pen React lifycycle testing by zhangmeng (@zhangmeng712) on CodePen.

二、你应该掌握的API用法

1、常用 API

  • React.createClass
  • React.render
  • React.findDOMNode
  • React.createElement:创建一个virtual dom进行渲染,可以使用React.render进行加载
  • React.Children: 用于处理组件中的this.props.children
    • React.Children.map
    • React.Children.only
    • React.Children.forEach
    • React.Children.count

2、组件相关API

  • this.setState:设置state,除了支持key value的传送方式,还支持传入fn, 注意通过上述的描述可以调用它的生命周期方法为:componentDidMount以及componentWillMount(不会触发刷新render,但是可以赋值)。
  • this.setProps:设置props
  • this.props.children:表示在实例化的时候传入组件的所有子节点,可以通过React.Children来统一处理渲染到页面上
  • this.props.refs:render中Dom Node可以用ref来标示,这样就可以利用React.findDOMNode(this.refs.xx)来获取对应的DOM元素了。ref可以传入名称,也可以传入函数更多详情
//setState的函数传入,入参为之前的state对象,还有当前的props对象
 this.setState(function (prevState, currentProps) {
                                return {
                                    stateValue: 'new stateValue'
                                }
                            });


//操作this.props.children
 var ComponentBox = React.createClass({
    render: function () {
          return (<ul>
                {
                       //能够渲染 
                 React.Children.map(this.props.children, function (child) {
                            return <li>{child}</li>
                        })

                }
                {
                        //  返回不是object ?渲染不出
                        React.Children.forEach(this.props.children, function (child) {
                            return <li>{child}</li>
                        })

                }
            </ul>)
        } 
});
var compInstance = React.render(
            <ComponentBox><span>list1</span><span>list2</span></ComponentBox>,
            document.getElementById('content') //DOM Element
    );

//refs的用法
<input ref={ function(component){ React.findDOMNode(component).focus();} } />

<input type="text" ref="myTextInput" defaultValue={this.props.propValue} />
 var input = React.findDOMNode(this.refs.myTextInput)

3、Add-on API

  • Animation API:
    • ReactCSSTransitionGroup 用于控制动画的标签。
    • transitionName 定义动画的class前缀,以下例为基础默认在标签内新增的的元素,动画效果的className为example-enter example-enter-active;元素被删除时候效果的className为 example-leave example-leave-active
    • transitionAppear v0.13开始有的方法,用于书写初始化的动画效果,默认是false。注意:一定要先让ReactCSSTransitionGroup这个标签渲染出来,然后再在标签内增加元素,否则效果不生效。
    • 有人在使用这个Add-on的时候遇到了动画时序的问题,也可以使用自定义的动画解决,参考这个例子
     <ReactCSSTransitionGroup transitionName="example" transitionAppear={true}>
                        {items}
                        </ReactCSSTransitionGroup>
    

See the Pen react animtion test by zhangmeng (@zhangmeng712) on CodePen.

组件的拆分和数据的传递

一般来说,稍微复杂的组件可以被拆分成若干组件(拆分本着一个组件只做一件事情的原则,参见Single_responsibility_principle)。合理的组件拆分会让React组件的开发复用性更强,那么在组件中如何管理组件间数据的传递?具体可以参考《How to communicate between React components》这篇文章,讲解的非常详细。

参考资料

  • https://github.com/uberVU/react-guide/blob/master/props-vs-state.md
  • http://ctheu.com/2015/02/12/how-to-communicate-between-react-components/#child_to_parent
  • http://facebook.github.io/react/docs/thinking-in-react.html