Rails×Reactでユーザー画面の色をアカウントごとに切り替える


#はじめに
SaaS型プロダクトで、管理画面側(Shop)とエンドユーザー側(User)との2つがあるプロダクトを開発しています。管理画面側(Shop)は、Shopアカウントごとにログインを行います。
Ruby on RailsとReact(TypeScript)、Material-UIで開発しています。


#やりたいこと
アカウントごとにそれぞれ別の色を設定し、それが各アカウントごとのエンドユーザー側の画面のマスター色に反映されるようにしたい。
例： アカウントA(ShopA)の色は赤色、アカウントB(ShopB)の色は青色。ユーザーはShopAのエンドユーザー画面に入ると赤色がマスター色(ヘッダーやボタンなど)、ShopBのエンドユーザー画面は青色がマスター色。

#方針
1. Shop_colorテーブルを追加、ShopテーブルがShop_colorテーブルを1つ持つ(has_one)
2. カラーコード(例：#FFFFFF)を一旦追加。
3. RailsControllerで、ShopのShop_colorを取得しReact側に送信、Shop_colorがnullであればデフォルトを代入
4. React側で受け取ったShop_colorをボタンやヘッダーなどの色に指定する


最初は、CSS変数(カスタムプロパティ)とMaterial-UIの```ThemeTemplate```で対処しようとしていたのですが、Propsで渡す方が楽なのでpropsで処理することにしました。


#実装

##1. Shop_colorテーブルを追加。

```console:console
rails g model ShopColor shop_id:string color_code:string
```



以下のmigrationファイルが生成。

```ruby:migration
class CreateShopColors < ActiveRecord::Migration[6.0]
  def change
    create_table :shop_colors do |t|
      t.integer :shop_id
      t.string :color_code

      t.timestamps
    end
  end
end
```

モデルではshopにbelongs_to、shop側ではhas_oneさせる。

```ruby:shop_color.rb
class ShopColor < ApplicationRecord
    belongs_to :shop
end
```




```ruby:shop.rb
class Shop < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable

  省略
  has_one :shop_color
end

```

migrationファイルをあげる。

```console:console
bundle exec rake db:migrate
```

##2. カラーコードを一旦追加

カラーコード を一旦手動でDBに登録

```console:console
rails c 
 > a = ShopColor.new
 > a.shop_id = 1
 > a.color_code = "#AAAAAA" #カラーコード
 > a.save!

```

##3. RailsControllerの処理

```ruby:UserController
#省略
def shop_show
    @target_shop = Shop.find_by(id: params[:shop_id])
    @shop_color = @target_shop.shop_color.color_code ||= "#ABCDEF"  #shop_color.color_codeがない場合用にデフォルトのカラーコード を右辺に
end
#省略
```


##4. React側の処理 

```ruby:shop_show.html.erb
<%= react_component("templates/users/user_managements/shop_menu", {
    shop: @target_shop,
    shopColor: @shop_color,
    #省略
}) %>
```


```React:shop_show.tsx


type Props = {
  shop: {
    id: number
    name: string
  }
  shopColor: string
}

//クラスで指定する場合
const useStyles = makeStyles<Theme, Props>((theme: Theme) =>
  createStyles({
    button: {
      backgroundColor:  (props)=> props.shopColor,
    },
  })
 )

//returnの中で書き込む場合
const ShopShow: React.FC<Props> = (props: Props) => {
const classes = useStyles(props);
 return (
 <ThemeTemplate>
  <Typography style={{color: props.shopColor}}>
   Hello
  </Typography>
  <Button className={classes.button} > 
    クラス指定のボタン
  </Button>
 </ThemeTemplate>
 )
}

export default ShopShow
```

ヘッダーなどのAppBarで利用する場合は、呼び出す際にpropsで渡す。Appbar.tsx側でも上記と同様に処理

```React:xxx

<AppBarUserComponent isSignedIn={props.isSignedIn} shop={props.shop} shopColor={props.shopColor}/>
```

#処理の流れ

1. UserControllerで```shop_show```メソッドが呼ばれる。```@shop_color```にカラーコード を詰める
2. shop_show.html.erbが呼ばれ、```@shop_color```を```shopColor```でreactコンポーネントの```shop_menu.tsx```に渡す
3. shop_menu.tsxでは```Props```で```shopColor```の型を用意。
4. shop_menu.tsxの```const ShopShow: React.FC<Props> = (props: Props) => ```の最初で、```const classes = useStyles(props);```で```props```を```useStyles```に渡す。
4. shop_menu.tsxの```useStyles```で、```makeStyles<Theme, Props>```で```Props```を受け取り、```backgroundColor:  (props)=> props.shopColor```で色を指定する

#おわりに
ユーザーごとに背景色を変えるなどの処理は良くあるかと思いますので参考になれば嬉しいです。


##参考
[reactjs : TSのmakeStyleに小道具を渡す](https://www.fixes.pub/program/224828.html)