【React.js】JSXでhrefの文字列と値を連結する

文字列の連結は、{‘hoge’ + value} と言うように、{}の中で連結する

render() {
    if (this.state.data) {
      return <div>
        <table class="table">
        <thead>
        <tr>
          <th scope="col">Height</th>
          <th scope="col">Time</th>
          <th scope="col">Hash</th>
        </tr>
        </thead>
        <tbody>
          {this.state.data.map((value) => (
            <tr>
              <th scope="row"><a href={'/data/' + value.height} >{value.height}</a></th>
              <td>{value.time}</td>
              <td>{value.hash}</td>
            </tr>
          ))}
        </tbody>
        </table>
      </div>
    }
    else {
      return <div>Loading...</div>
    }
  }

リンクは以下のように設定されます
http://***/data/1

ここに辿り着くまでに結構時間かかりました… OK

【Rust/React.js】axumでReactのコンポーネントを利用する

### React
まず、reactで実装したコードをbuildする
$ npm run build

すると、build/static/js にjsファイル一群ができるので、その中からmain.*.jsをコピーする
axumのルートディレクトリにstaticフォルダを作成して、./static/js/script.js にbuildされたjsのスクリプトを貼り付ける。

### axum側
dependencyに tower-httpを追加
tower-http = { version = “0.6.2”, features = [“fs”] }

templatesのhtmlからjsファイルを読み込めるように、ルーティングで読み込めるようにする

use tower_http::services::{ServeDir, ServeFile};

#[tokio::main]
async fn main() {
    let serve_dir = ServeDir::new("static").not_found_service(ServeFile::new("static"));

    let app = Router::new()
        .route("/", get(handler_index))
        .nest_service("/static", serve_dir.clone())
        .fallback_service(serve_dir.clone());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

templates/index.html

<body>
<h1>Hello world</h1>
<div id="root">wait...</div>
<script src="./js/script.js"></script>
</body>
</html>

なるほど、いけますね。あとは作り込みのみ!

【Rust/React.js】jsonで保存したデータをsetTimeoutで表示する

React(Json)で、入れ子のデータを扱うには、data.map((value) => と書くと、for(let d in data) のような処理ができる。

main.rs

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Name {
    first : String,
    last: String,
}

#[tokio::main]
async fn main()  {
    let mut names : Vec<Name> = Vec::new();
    let n1 = Name { first:"taro".to_string(), last:"yamata".to_string()};
    let n2 = Name { first:"hajime".to_string(), last:"tanaka".to_string()};
    let n3 = Name { first:"hanako".to_string(), last:"yoshida".to_string()};
    names.push(n1);
    names.push(n2);
    names.push(n3);
    println!("{:?}", names);
    let serialized: Vec<u8> = serde_json::to_vec(&names).unwrap();
    println!("{:?}", serialized);
    let file_path = format!("./data/data.txt");
    let mut file = File::create(file_path.clone()).unwrap();
    file.write_all(&serialized).expect("write failed");

}

data.txt

[{"first":"taro","last":"yamata"},{"first":"hajime","last":"tanaka"},{"first":"hanako","last":"yoshida"}]

App.js

import React, {Component} from 'react';
import './App.css';
 
class App extends Component {
 
  render() {
    return <div className="App">
    <h1>React</h1>
    <Blocks />
  </div>;
  }
}
 
class Blocks extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: null,
    };
  }

  componentDidMount() {
      this.timer = setInterval(
          () => 
              fetch('./data/data.text').then(response => {
                  return response.json();
                  }).then(json => {
                      this.setState({ data: json});
                  }),
          2000,
      );
  }

  componentWillUnmount() {
      clearInterval(this.timer);
  }

  render() {
    if (this.state.data) {
      return <div>
        {this.state.data.map((value) => (
          <p>名前:{value.first} {value.last}</p>
        ))}
      </div>
    }
    else {
      return <div>Loading...</div>
    }
  }
}
export default App;

これを少し応用したい。

【React.js】setTimoutでJsonファイルを取得して表示したい

jsonを用意します。

{
    "name": "Morpheush1"
}
let dom = document.querySelector('#root');

class Welcome extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: null,
      };
    }
  
    componentDidMount() {
        this.timer = setInterval(
            () => 
                fetch('./data/test.json').then(response => {
                    return response.json();
                    }).then(json => {
                        this.setState({ data: json});
                    }),
            1000,
        );
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }
  
    render() {
      if (this.state.data) {
        return <p>{this.state.data.name}</p>
      }
      else {
        return <div>Loading...</div>
      }
    }
  }

let el = (
    <div>
        <Welcome/>
    </div>
);

ReactDOM.render(el, dom);

jsonを更新すると、ページも更新されるのがわかります。

OKやな

jsonが入れ子の場合

[{"name": "Morpheush4"},{"name": "Morpheush5"}]

js側
this.state.data[0]

サーバ側でデータを作る際に、入れ子のjsonデータを作らないといけない。
なるほど〜 これはちょっと厄介やな…

【React.js】コンポーネントの様々な機能

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

class App extends Component {

  input = '';

  msgStyle = {
    fontSize:"20pt",
    color:"#900",
    margin:"20px 0px",
    padding: "5px",
  }
  inputStyle = {
    fontSize: "12pt",
    padding: "5px"
  }

  constructor(props) {
    super(props);
    this.state = {
      message:'type your name:'
    };
    this.doChange = this.doChange.bind(this);
    this.doSubmit = this.doSubmit.bind(this);
  }

  doChange(event) {
    this.input = event.target.value;
  }

  doSubmit(event) {
    this.setState({
      message: 'Hello, ' + this.input + '!!'
    });
    event.preventDefault();
  }
  render() {
    return <div className="App">
    <h1>React</h1>
    <h2>{this.state.message}</h2>
    <form onSubmit={this.doSubmit}>
      <label>
        <span style={this.inputStyle}></span>Message:
        <input type="text" style={this.inputStyle} onChange={this.doChange} required pattern="[A-Za-z _,.]+"/>
      </label>
      <input type="submit" style={this.inputStyle} value="Click" />
    </form>
  </div>;
  }
}

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

class App extends Component {

  input = '';

  msgStyle = {
    fontSize:"20pt",
    color:"#900",
    margin:"20px 0px",
    padding: "5px",
  }
  constructor(props) {
    super(props);
    this.state = {
      message:'type your name:'
    };
    this.doCheck = this.doCheck.bind(this);
  }

  doCheck(event) {
    alert(event.target.value +
      "は長すぎます。(最大10文字)"
    );
  }

  render() {
    return <div className="App">
    <h1>React</h1>
    <h2>{this.state.message}</h2>
    <Message maxlength="10" onCheck={this.doCheck} />
  </div>;
  }
}

class Message extends Component {
  inputStyle = {
    fontSize:"12pt",
    padding:"5px"
  }

  constructor(props) {
    super(props);
    this.doChange = this.doChange.bind(this);
  }

  doChange(e) {
    if(e.target.value.length > this.props.maxlength) {
      this.props.onCheck(e);
      e.target.value =
        e.target.value.substr(0,this.props.maxlength);
    }
  }

  render() {
    return <input type="text" style={this.inputStyle} onChange={this.doChange} />
  }
}

export default App;

### コンテキスト

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

let data = {title: 'Title',
  message: 'this is sample message.'
};

const SampleContext = React.createContext(data);

class App extends Component {

  render() {
    return <div className="App">
    <h1>Context</h1>
    <Title />
    <Message />
  </div>;
  }
}

class Title extends Component {
  static contextType = SampleContext;

  render() {
    return (
      <div>
        <h2>{this.context.title}</h2>
      </div>
    );
  }
}

class Message extends Component {
  static contextType = SampleContext;

  render() {
    return (
      <div>
        <p>{this.context.message}</p>
      </div>
    )
  }
}

export default App;
import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

let theme = {
  light: {
    backgroundColor: "#eef",
    color: "#006",
    padding:"10px",
  },
  dark: {
    backgroundColor:"#006",
    color:"#eef",
    padding:"10px",
  }
};

const ThemeContext = React.createContext(theme.dark);

class App extends Component {

  static contextType = ThemeContext;

  render() {
    return <div className="App" style={this.context}>
    <h1>Context</h1>
    <Title value="Content page" />
    <Message value="This is Content sample."/>
    <Message value="これはテーマのサンプルです。"/>
  </div>;
  }
}

class Title extends Component {
  static contextType = ThemeContext;

  render() {
    return (
      <div>
        <h2 style={this.context}>{this.props.value}</h2>
      </div>
    );
  }
}

class Message extends Component {
  static contextType = ThemeContext;

  render() {
    return (
      <div>
        <p style={this.context}>{this.props.value}</p>
      </div>
    )
  }
}

export default App;

【React.js】プロパティとステート

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

class App extends Component {

  data = [];

  msgStyle = {
    fontSize:"20pt",
    color:"#900",
    margin:"20px 0px",
    padding: "5px",
  }
  area = {
    width: "500px",
    height: "500px",
    border: "1px solid blue"
  }

  constructor(props) {
    super(props);
    this.state = {
      list: this.data
    };
    this.doAction = this.doAction.bind(this);
  }

  doAction(e) {
    let x = e.pageX;
    let y = e.pageY;
    this.data.push({x:x, y:y});
    this.setState({
      list:this.data
    });
  }

  draw(d) {
    let s = {
      position:"absolute",
        left: (d.x - 25) + "px",
        top:(d.y - 25) + "px",
        width: "50px",
        height: "50px",
        backgroundColor: "#66f3",
    };
    return <div style={s}></div>
  }

  render() {
    return <div className="App">
    <h1>React</h1>
    <h2 style={this.msgStyle}>show rect. </h2>
    <div style={this.area} onClick={this.doAction}>
      {this.data.map((value) => this.draw(value))}
    </div>
  </div>;
  }
}

export default App;

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

class App extends Component {

  data = [
    "This is list sample",
    "これはリストのサンプルです。",
    "配列をリストに変換します。"
  ];

  msgStyle = {
    fontSize:"20pt",
    color:"#900",
    margin:"20px 0px",
    padding: "5px",
  }

  constructor(props) {
    super(props);
    this.state = {
      list: this.data
    };
  }

  render() {
    return <div className="App">
    <h1>React</h1>
    <h2 style={this.msgStyle}>show list.</h2>
    <List title="サンプル・リスト" data={this.data} />
  </div>;
  }
}

class List extends Component {
  number = 1;

  title = {
    fontSize:"20pt",
    fontWeight:"bold",
    color:"blue",
  };

  render() {
    let data = this.props.data;
    return (
      <div>
        <p style={this.title}>{this.props.title}</p>
        <ul>
          {data.map((item) =>
            <Item number={this.number++} value={item} key={this.number} />
            )}
        </ul>
      </div>
    );
  }
}

class Item extends Component {
  li = {
    listStyleType:"square",
    fontSize:"16pt",
    color:"#06",
    margin:"0px",
    padding: "0px",
  }
  num = {
    fontWeight:"bold",
    color:"red"
  }

  render() {
    return (
      <li style={this.li}>
        <span style={this.num}>[{this.props.number}]</span>
         {this.props.value}
      </li>
    )
  }
}
export default App;

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

class App extends Component {

  input = '';

  msgStyle = {
    fontSize:"20pt",
    color:"#900",
    margin:"20px 0px",
    padding: "5px",
  }

  constructor(props) {
    super(props);
    this.state = {
      message:'type your name:'
    };
    this.doChange = this.doChange.bind(this);
    this.doSubmit = this.doSubmit.bind(this);
  }

  doChange(event) {
    this.input = event.target.value;
  }

  doSubmit(event) {
    this.setState({
      message: 'Hello, ' + this.input + '!!'
    });
    event.preventDefault();
  }
  render() {
    return <div className="App">
    <h1>React</h1>
    <Message title="Children">
      これはコンポーネント内のコンテンツです。
      マルでテキストを分割し、リストにして表示します。
      改行は必要ありません。
    </Message>
  </div>;
  }
}

class Message extends Component {
  li = {
    fontSize:"16pt",
    color:"#06",
    margin:"0px",
    padding: "0px",
  }

  render() {
    let content = this.props.children;
    let arr = content.split('。');
    let arr2 = [];
    for(let i = 0; i < arr.length;i++){
      if(arr[i].trim() != '') {
        arr2.push(arr[i]);
      }
    }
    let list = arr2.map((value,key) => (
      <li style={this.li} key={key}>{value}.</li>
    ));
    return <div>
      <h2>{this.props.title}</h2>
      <ol>{list}</ol>
    </div>
  }
}
export default App;

【React.js】プロジェクトでコンポーネントの開発

App.js

import React, {Component} from 'react';
import './App.css';

class App extends Component {

  render() {
    return <div className="App">
    <h1>React</h1>
    <Rect x="50" y="50" w="150" h="150" c="cyan" />
    <Rect x="150" y="100" w="150" h="150" c="magenta" />
    <Rect x="100" y="150" w="150" h="150" c="black" />
  </div>;
  }
}

class Rect extends Component {
  x = 0;
  y = 0;
  width = 0;
  height = 0;
  color = "white";
  style = {};

  constructor(props) {
    super(props);
    this.x = props.x;
    this.y = props.y;
    this.width = props.w;
    this.height = props.h;
    this.color = props.c;
    this.style = {
      backgroundColor:this.color,
      position:"absolute",
      left:this.x + "px",
      top:this.y + "px",
      width:this.width + "px",
      height:this.height + "px"
    }
  }
  render(){
    return <div style={this.style}></div>
  }
}

export default App;

### ファイルの分割
Rect.js

import React, {Component} from 'react';

class Rect extends Component {
    x = 0;
    y = 0;
    width = 0;
    height = 0;
    color = "white";
    style = {};
  
    constructor(props) {
      super(props);
      this.x = props.x;
      this.y = props.y;
      this.width = props.w;
      this.height = props.h;
      this.color = props.c;
      this.radius = props.r;
      this.style = {
        backgroundColor:this.color,
        position:"absolute",
        left:this.x + "px",
        top:this.y + "px",
        width:this.width + "px",
        height:this.height + "px",
        borderRadius:this.radius + "px"
      }
    }
    render(){
      return <div style={this.style}></div>
    }
  }

export default Rect;

App.js

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

class App extends Component {

  render() {
    return <div className="App">
    <h1>React</h1>
    <Rect x="50" y="50" w="150" h="150" c="cyan" r="50" />
    <Rect x="150" y="100" w="150" h="150" c="magenta" r="75" />
    <Rect x="100" y="150" w="150" h="150" c="black" r="25" />
  </div>;
  }
}

export default App;

### State

import React, {Component} from 'react';
import Rect from "./Rect";
import './App.css';

class App extends Component {

  msgStyle = {
    fontSize:"24pt",
    color:"#900",
    margin:"20px 0px",
    padding: "5px",
    borderBottom: "2px solid #900"
  }

  constructor(props) {
    super(props);
    this.state = {
      msg: 'Hello Component.',
    };
  }

  render() {
    return <div className="App">
    <h1>React</h1>
    <p style={this.msgStyle}>{this.state.msg}</p>
    <p style={this.msgStyle}>{this.props.msg}</p>
  </div>;
  }
}
  constructor(props) {
    super(props);
    this.state = {
      msg: 'Hello',
    };
    let timer = setInterval(() => {
      this.setState((state) => ({
        msg: state.msg + "!"
      }));
    }, 10000);
  }
  constructor(props) {
    super(props);
    this.state = {
      counter:0,
      msg: 'count start!',
    };
    this.doAction = this.doAction.bind(this);
  }

  doAction(e) {
    this.setState((state) => ({
      counter: state.counter + 1,
      msg: 'count: ' + state.counter
    }));
  }

  render() {
    return <div className="App">
    <h1>React</h1>
    <p style={this.msgStyle}>{this.state.msg}</p>
    <button style={this.btnStyle} onClick={this.doAction}>Click</button>
  </div>;
  }

【React.js】コンポーネントの理解

let dom = document.querySelector('#root');

const msg = {
    fontSize: "20pt",
    fontWeight: "bold",
    padding: "10px",
    color: "white",
    backgroundColor: "darkblue"
};

function Welcome(props) {
    return <p style={msg}>Hello React!!</p>;
}

let el = (
    <div>
        <Welcome />
    </div>
);
ReactDOM.render(el, dom);

### 属性の使用

let dom = document.querySelector('#root');

const msg1 = {
    fontSize: "20pt",
    padding: "10px",
    border: "double 5px magenta"
};

const msg2 = {
    fontSize: "16pt",
    fontWeight: "bold",
    padding: "10px",
    backgroundColor: "cyan"
};

function Welcome(props) {
    return <p style={props.style}>Hello, {props.name}!!</p>;
}

let el = (
    <div>
        <Welcome name="Taro" style={msg1} />
        <Welcome name="Hanako" style={msg2} />
    </div>
);
ReactDOM.render(el, dom);

### 計算するコンポーネント

let dom = document.querySelector('#root');

const msg = {
    fontSize: "16pt",
    fontWeight: "bold",
    padding: "10px",
    color: "white",
    backgroundColor: "darkblue"
};

function Calc(props) {
    let total = 0;
    for (let i = 1; i <= props.number; i++) {
        total += i;
    }
    return <p style={msg}>1から{props.number}までの合計は、「{total}」です。</p>;
}

let el = (
    <div>
        <Calc number="100" />
        <Calc number="200" />
        <Calc number="300" />
    </div>
);
ReactDOM.render(el, dom);

let dom = document.querySelector('#root');

class Rect extends React.Component {
    x = 0;
    y = 0;
    width = 0;
    height = 0;
    color = "white";
    style = {};

    constructor(props) {
        super(props);
        this.x = props.x;
        this.y = props.y;
        this.width = props.w;
        this.height = props.h;
        this.color = props.c;
        this.style = {
            backgroundColor:this.color,
            position:"absolute",
            left:this.x + "px",
            top:this.y + "px",
            width:this.width + "px",
            height: this.height + "px"
        }
    }

    render() {
        return <div style={this.style}></div>;
    }
}

let el = (
    <div>
        <Rect x="100" y="100" w="100" h="100" c="cyan" />
        <Rect x="150" y="150" w="100" h="100" c="magenta" />
    </div>
);
ReactDOM.render(el, dom);

【React】react-hook-formのバリデーション

import './App.css';
import { useForm } from 'react-hook-form';

function App() {
  const { register, 
  handleSubmit,
  formState: {errors},
  } = useForm();

  const onSubmit = (data) => console.log(data);

  return (
    <div className="App">
      <h1>login</h1>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" {...register('email', { required: true })} />
          {errors.email && <div>入力が必須の項目です</div>}
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            {...register('password')} type="password" 
          />
        </div>
        <div>
          <button type="submit">Login</button>
        </div>
      </form>
    </div>
  );
}

複数のエラーメッセージ

function App() {
  const { register, 
  handleSubmit,
  formState: {errors},
  } = useForm({
    criteriaMode: 'all',
  });

<label htmlFor="password">パスワード</label>
          <input
            id="password"
            {...register('password', {
              required: {
                value: true,
                message: '入力が必須の項目です。',
              },
              pattern: {
                value: /^[A-Za-z]+$/,
                message: 'アルファベットのみ入力してください。'
              },
              minLength: {
                value: 8,
                message: '8文字以上入力してください。'
              }
            })} type="password" 
          />
          {errors.password?.type === 'required' && (<div>{errors.password.types.required}</div>)}
          {errors.password?.type === 'pattern' && (<div>{errors.password.types.pattern}</div>)}
          {errors.password?.type === 'minLength' && (<div>8文字以上入力してください。</div>)}
        </div>

バリデーションのタイミングは制御できる
reValidateMode: ‘onSubmit’,
mode: ‘onChange’,

なるほど、何となくわかってきました。
さぁ、実装していきますか…

【React】react-hook-formを使ってみる

$ yarn add react-hook-form

react-hook-formを使わずに書いた場合 1

import './App.css';
import { useState } from 'react';

function App() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({
      email,
      password,
    });
  };

  const handleChangeEmail = (e) => {
    setEmail(e.target.value);
  };
  const handleChangePassword = (e) => {
    setPassword(e.target.value);
  };
  return (
    <div className="App">
      <h1>login</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" name="email" value={email} onChange={handleChangeEmail} />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={password}
            onChange={handleChangePassword}
            type="password"
          />
        </div>
        <div>
          <button type="submit">Login</button>
        </div>
      </form>
    </div>
  );
}

export default App;

react-hook-formを使わずに書いた場合 2

import './App.css';
import { useRef } from 'react';
import 

function App() {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({
      email: emailRef.current.value,
      password: passwordRef.current.value,
    });
  };

  // const handleChangeEmail = (e) => {
  //   setEmail(e.target.value);
  // };
  // const handleChangePassword = (e) => {
  //   setPassword(e.target.value);
  // };
  return (
    <div className="App">
      <h1>login</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" name="email" ref={emailRef} />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            ref={passwordRef} type="password" 
          />
        </div>
        <div>
          <button type="submit">Login</button>
        </div>
      </form>
    </div>
  );
}

export default App;

### react-hook-formを使った場合

import './App.css';
import { useForm } from 'react-hook-form';

function App() {
  const { register, handleSubmit } = useForm();

  const onSubmit = (data) => console.log(data);

  return (
    <div className="App">
      <h1>login</h1>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" {...register('email')} />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            {...register('password')} type="password" 
          />
        </div>
        <div>
          <button type="submit">Login</button>
        </div>
      </form>
    </div>
  );
}

export default App;

registerに入っている値

const { name, ref, onChange, onBlur } = register('email');
//略
<input
  id="email"
  name={name}
  onChange={onChange}
  onBlur={onBlur}
  ref={ref}
/>