🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

Material-UI

Material-UIは、Material Designを利用可能なオープンソースのReact向けUIコンポーネントキットです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

889閲覧

React+Redux+Material-UI error : is not a function

khhhhh

総合スコア5

Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

Material-UI

Material-UIは、Material Designを利用可能なオープンソースのReact向けUIコンポーネントキットです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

1クリップ

投稿2019/09/15 08:33

編集2019/09/15 14:22

はじめに

React + Redux + Material-UI で簡単なアプリケーションを開発しています。
現在はMaterial-UIのDrawerを実装しているのですが、
connect()でComponentに渡した関数を使用すると下記のようなエラーが発生します。
どなたかご教授お願い致します。

発生している問題・エラーメッセージ

TypeError: this.props.handleToggleDrawer is not a function onClick src/components/header/NavIcon.js:79 76 | <IconButton 77 | color="inherit" 78 | aria-label="Menu" > 79 | onClick={() => this.props.handleToggleDrawer()} 80 | ^ className={classes.navIcon} 81 | > 82 | <MenuIcon />

該当のソースコード

actions/NavIconAction

1/** 2 * action/NavIconAction.js 3 */ 4import { TOGGLE_DRAWER } from "../utils/actionTypes"; 5 6export const toggleDrawer = () => { 7 return { 8 type: TOGGLE_DRAWER, 9 } 10}

reducer

1/** 2 * reducers/NavIconReducer.js 3 */ 4import * as actionTypes from '../utils/actionTypes' 5 6const initialState = { 7 open: false, 8} 9 10export default function NavIconReducer(state = initialState, action) { 11 switch (action.type) { 12 case actionTypes.TOGGLE_DRAWER: 13 console.log('open: ' + !state.open) 14 return Object.assign({}, state, { 15 open: !state.open, 16 }) 17 default: 18 return state 19 } 20}

reducer/index.js

1/** 2 * reducers/index.js 3 */ 4import { combineReducers } from 'redux'; 5import NavIconReducer from './NavIconReducer'; 6import ShareIconReducer from './ShareIconReducer'; 7import NotificationReducer from './NotificationReducer'; 8 9const reducers = combineReducers({ 10 NavIconReducer, 11 ShareIconReducer, 12 NotificationReducer 13}); 14 15export default reducers;

container

1/** 2 * containers/NavIconContainer.js 3 */ 4import { connect } from 'react-redux'; 5import { toggleDrawer } from '../actions/NavIconAction' 6import NavIcon from '../components/header/NavIcon' 7 8const mapStateToProps = state => ({ 9 open: state.NavIconReducer.open, 10}) 11 12const mapDispatchToProps = dispatch => { 13 return{ 14 handleToggleDrawer: () => dispatch(toggleDrawer()) 15 } 16} 17 18export default connect(mapStateToProps, mapDispatchToProps)(NavIcon)

component

1/** 2 * components/header/NavIcon.js 3 */ 4import React from 'react' 5import { withStyles } from '@material-ui/core/styles' 6import MenuIcon from '@material-ui/icons/Menu' 7import Hidden from '@material-ui/core/Hidden' 8import Drawer from '@material-ui/core/Drawer' 9import Divider from '@material-ui/core/Divider' 10import List from '@material-ui/core/List' 11import ListItem from '@material-ui/core/ListItem' 12import ListItemText from '@material-ui/core/ListItemText' 13import InfoIcon from '@material-ui/icons/Info'; 14import HomeIcon from '@material-ui/icons/Home'; 15import AccountCircle from '@material-ui/icons/AccountCircle'; 16import LockOpen from '@material-ui/icons/LockOpen'; 17import HowToReg from '@material-ui/icons/HowToReg'; 18import { ListItemIcon } from '@material-ui/core'; 19import IconButton from '@material-ui/core/IconButton'; 20 21const drawerWidth = 250 // Width for Drawer 22 23const styles = theme => ({ 24 navIcon: { 25 [theme.breakpoints.up('md')]: { 26 display: 'none', 27 }, 28 }, 29 drawer: { 30 }, 31 drawerPaper: { 32 width: drawerWidth, 33 }, 34}); 35 36class NavIcon extends React.Component { 37 render() { 38 const { theme, classes } = this.props 39 40 const drawer = ( 41 <div> 42 <Divider /> 43 <List> 44 <ListItem button> 45 <ListItemIcon><InfoIcon /></ListItemIcon> 46 <ListItemText primary="***** とは" /> 47 </ListItem> 48 </List> 49 <Divider /> 50 <List> 51 <ListItem button> 52 <ListItemIcon><HomeIcon /></ListItemIcon> 53 <ListItemText primary="トップページ" /> 54 </ListItem> 55 </List> 56 <List> 57 <ListItem button> 58 <ListItemIcon><AccountCircle /></ListItemIcon> 59 <ListItemText primary="マイページ" /> 60 </ListItem> 61 </List> 62 <List> 63 <ListItem button> 64 <ListItemIcon><LockOpen /></ListItemIcon> 65 <ListItemText primary="ログイン" /> 66 </ListItem> 67 </List> 68 <List> 69 <ListItem button> 70 <ListItemIcon><HowToReg /></ListItemIcon> 71 <ListItemText primary="新規会員登録" /> 72 </ListItem> 73 </List> 74 </div> 75 ) 76 77 return ( 78 <div> 79 <IconButton 80 color="inherit" 81 aria-label="Menu" 82 onClick={() => this.props.handleToggleDrawer()} 83 className={classes.navIcon} 84 > 85 <MenuIcon /> 86 </IconButton> 87 <nav className={classes.drawer} aria-label="link"> 88 <Hidden mdUp> {/* md未満 */} 89 <Drawer 90 variant="temporary" 91 anchor={theme.direction === 'rtl' ? 'right' : 'left'} 92 open={this.props.open} 93 onClose={() => this.props.handleToggleDrawer()} 94 classes={{ 95 paper: classes.drawerPaper, 96 }} 97 ModalProps={{ 98 keepMounted: true, // Better open performance on mobile. 99 }} 100 > 101 {drawer} 102 </Drawer> 103 </Hidden> 104 <Hidden smDown implementation="css"> {/* md以上 */} 105 <Drawer 106 variant="permanent" 107 open 108 classes={{ 109 paper: classes.drawerPaper, 110 }} 111 > 112 {drawer} 113 </Drawer> 114 </Hidden> 115 </nav> 116 </div> 117 ) 118 } 119} 120 121export default withStyles(styles, { withTheme: true })(NavIcon)
/** * components/header/index.js */ import React from 'react' import { withStyles } from '@material-ui/core/styles' import AppBar from '@material-ui/core/AppBar' import Toolbar from '@material-ui/core/ToolBar' import Typography from '@material-ui/core/Typography'; import NavIcon from './NavIcon' import ShareIcon from './ShareIcon' import logo from '../../logo.png' const drawerWidth = 250 // Width for Drawer const styles = theme => ({ appBar: { width: '100%', marginLeft: drawerWidth, [theme.breakpoints.up('md')]: { width: `calc(100% - ${drawerWidth}px)`, }, }, toolBar: { }, title: { margin: 'auto', }, logo: { width: 20, paddingRight: 5, }, }) const Header = ({ classes }) => ( <div className={classes.header}> <AppBar className={classes.appBar}> <Toolbar className={classes.toolBar}> <NavIcon /> <Typography className={classes.title}> <img src={logo} alt="Header logo" className={classes.logo} /> ぐるめなう </Typography> </Toolbar> </AppBar> </div> ) export default withStyles(styles, { withTheme: true })(Header)

App

1/** 2 * App.js 3 */ 4import React from 'react'; 5import { Route, Switch } from 'react-router-dom' 6import { makeStyles } from '@material-ui/core/styles' 7import CssBaseline from '@material-ui/core/CssBaseline' 8import Header from './components/header' 9import About from './components/about' 10import Top from './components/top' 11import MyPage from './components/mypage' 12import Login from './components/login' 13import Signup from './components/signup' 14 15const useStyles = makeStyles(() => ({ 16 root: { 17 }, 18})) 19 20function App() { 21 const classes = useStyles(); 22 23 return ( 24 <CssBaseline> 25 <div className={classes.root}> 26 <Header /> 27 <Switch> 28 <Route exact path="/" component={Top} /> 29 <Route exact path="/mypage" component={MyPage} /> 30 <Route exact path="/login" component={Login} /> 31 <Route exact path="/signup" component={Signup} /> 32 <Route exact path="/about" component={About} /> 33 </Switch> 34 </div> 35 </CssBaseline> 36 ); 37} 38 39export default App;

index.js

1/** 2 * index.js 3 */ 4import React from 'react' 5import ReactDOM from 'react-dom' 6import { Provider } from 'react-redux'; 7import App from './App' 8import { createStore } from 'redux'; 9import reducers from './reducers'; 10 11const store = createStore(reducers) 12 13ReactDOM.render( 14 <Provider store={store}> 15 <App /> 16 </Provider>, 17 document.getElementById('root') 18)

試したこと

MenuIconをクリックすると上記のエラーが発生します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jun68ykt

2019/09/15 13:46

以下、質問への追記要望が一点 (1) と、ソースコードを拝読して疑問に思ったこと二点、(2)(3) です。 (1) App.js の冒頭で import NavIcon from './NavIcon' とあります。これの意味するところは、ソースコードの入っているディレクトリーの中で、 App.js (や index.js ) と同じ階層に、 NavIcon.js というソースファイルがあるということでしょうか?もしあるのであれば、そのソースも質問に追記頂きたいです。 (2) containers/NavIconContainer.js で export default している、 Redux にconnectされたNavIconが、どこから import されているのかが、現状のソースコードを読む限りでは不明です。 (3) containers/NavIconContainer.js の mapStateToProps で、state.MenuIconReducer.open が props の open として渡ってくるような意図で書かれているが、 reducers/index.js でcombineされるのは、以下 { NavIconReducer, ShareIconReducer, NotificationReducer } となっていて、MenuIconReducer は無いので、 state.MenuIconReducer は undefined になり、 state.MenuIconReducer.open で Cannot read property 'open' of undefined のエラーになるのでは?と思いました。
khhhhh

2019/09/15 14:29

ご回答有り難うございます! 申し訳ございませんが、すべて誤植です…。変更させて頂きました。 (1)App.jsの中身は誤植です。編集いたしました。 (2)NavIconはcomponents/header/NavIcon.jsからimportしています。 componentsは共通のheader部分と、ページ単位で分けています。 ちなみにheaderフォルダの中身はindex.jsとNavIconなどが存在しており、その全てをheader/index.jsでまとめています。 (3)それも誤植です。state.NavIconReducer.openですね…。編集いたしました。 以上です。煩わしくて申し訳ございません。よろしくお願い致します。
khhhhh

2019/09/15 14:36 編集

App.js index.js components -header --index.js --NavIcon.js reducers -index.js -NavIconReducer.js containers -NavIconContainer.js actions -NavIconAction.js util -actionTypes.js
jun68ykt

2019/09/15 14:50

追記および修正ありがとうございます。回答しました。
guest

回答1

0

ベストアンサー

こんにちは

components/header/index.js の冒頭で

修正前:

javascript

1import NavIcon from './NavIcon'

としていますが、これによって import されるのは、 components/header/NavIcon.js に記載されている NavIcon コンポーネントですが、これは Redux に connect される前のコンポーネントです。
つまり、 containers/NavIconContainer.js にて redux に connect した後の NavIcon が使われていないです。これだと、props に handleToggleDrawer は渡されてこないので、 this.props.handleToggleDrawer()
とすると、ご質問にあるとおり、

this.props.handleToggleDrawer is not a function

のエラーになります。そこで、冒頭挙げた、修正前のimport行を以下のように修正してみるといかがでしょうか?

修正後:

javascript

1import NavIcon from '../../containers/NavIconContainer'

追記

ソースコードのディレクトリ構造に、 components/containers/ というサブディレクトリーを作ったときに、どのようにコンポーネントを類別して、 components/containers/ に振り分けていくか?というのはけっこう悩みどころかと思います。

小生の過去の react + redux の業務経験ですと、あるプロジェクトでは、

  • components/ にはredux にconnect しないコンポーネントを書き、components/ からimport したコンポーネントを redux にconnect させたコンポーネントを containers/ に入れる。(したがって components/ の中に、 redux にconnectしたコンポーネントを置くのは禁止)

というルールだったところもありますし、

  • components/ には部品的なものを入れて containers/ には、部品を配置した(いわば、"大きな" )コンポーネントを入れておく

というルールだったところもあります。

containers/ には、その名のとおり「コンテナ(=容器)であるコンポーネント」を入れておくわけですが、この、"コンテナであるコンポーネント" とそうでないものとの2つに大別するという考え方は、Redux 作者のひとりであるDan Abramovさんの以下の記事が原典かと思います。

個人的には、上記に箇条書きで書かれている、 presentational components と container components それぞれの特徴を、満たす条件ととらえてすべてを厳密に守ろうとすると、融通がきかなくなってしまいがち、という所感を持っています。(実際、上記の記事は、はじめ2015年に書かれたものですが、その後、冒頭にUpdate from 2019 として追記があり、追記の最後に

This text is left intact for historical reasons but don’t take it too seriously.

とDan氏自身が書いています。)

とはいえ、今でもひとつの目安にはなるので、上記の記事を、ご質問に挙げられているコードに適用してみると、container components の特徴の中には、

  • May contain both presentational and container components inside but usually don’t have any DOM markup of their own except for some wrapping divs, and never have any styles.

というものがありますが、これに沿うと、このご質問にある <Header /><NavIcon /> を含んでいるので、containerコンポーネントである、と言えなくもないです。

ご質問のコンポーネント構成では、

  • components/ にあるコンポーネント Xxx を Redux にconnectさせるソースファイルを XxxContainer.js として containers/ に作成する。

というルールにされているのかなと思いました。これはこれでアリな方針でありますが、これ以外の代替案として、

  • まず App から import されている以下

Header, About, Top, MyPage, Login, Signup

は、 container とする。これの根拠となるのは、「App から直接 import されるものは、ざっくり言って、大きな部品と考えて差し支えないから」という考え方です。

  • 上記 6 個のcontainerコンポーネントから import されるコンポーネントで、少なくない数の部品的なコンポーネントをimport して、ひとつのまとまった外観と機能を提供するものも containerコンポーネントとする。

  • 上記以外は、 ひとまず、components/ に入れておき、 その後の修正で、presentational component から container component に "格上げ" してもいいかなと思えたものは、その時点で適宜 containers/ に移動

  • components/ に入れておくものは Redux に connect してはダメというルールを、厳格に守ったほうがいいかはどうかはケースバイケースと考える。

特に、上記のリストの最後の考え方を採用してしまうと、

  • container なのか (presentationalな、)component なのかの判別に、redux に connectさせるかどうかは関係ない(少なくとも、判別の条件として、最優先されるわけではない。)

ということになるわけですが、これを「そんなことを認めたら、収拾がつかなくなるので認められない」と考えるか、「redux に connectさせるかどうかに縛られるよりも、 containerは大きな容れ物で、 component は containerに詰められる部品と考えたほうが、開発作業上のメリットが大きい」という考えを採用するかは、プロジェクトによって違っていました。

最後の考え方を採用すると、 components/header/NavIcon.js を redux に connect させたものを export してよいことになるので、以下のようにも書いてよいことになります。

jsx

1/** 2 * components/header/NavIcon.js 3 */ 4import React from 'react' 5 6・・・ 7 8class NavIcon extends React.Component { 9・・・ 10} 11 12// withStylesを適用したNavIconをいったん変数に入れておく。 13const StyledNavIcon = withStyles(styles, { withTheme: true })(NavIcon) 14 15// 以下にて、 StyledNavIcon を redux に connectして、これを export default する。 16const mapStateToProps = state => ({ 17 open: state.NavIconReducer.open, 18}) 19 20const mapDispatchToProps = dispatch => { 21 return{ 22 handleToggleDrawer: () => dispatch(toggleDrawer()) 23 } 24} 25 26// 上記の StyledNavIcon を redux にconnectする。 27export default connect(mapStateToProps, mapDispatchToProps)(StyledNavIcon) 28

そして、 containers/ のほうに移動した、 header/index.js にて、

jsx

1import NavIcon from '../../components/header/NavIcon'

として、redux に connect済みのNavIconを components/ からimportして使います。

もちろん上記の代替案のほうがいいといっているわけではありません。最終的にはメンバーの考えをもとにフロントエンドのリーダーが決定すべき、チームの方針かなと思います。

以上、参考になれば幸いです。

投稿2019/09/15 14:49

編集2019/09/15 19:36
jun68ykt

総合スコア9058

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

khhhhh

2019/09/15 15:10

なるほど! これは自分だけでは一生解決できていませんでした笑 Redux難しい…。 本当に助かりました!有り難うございました!
jun68ykt

2019/09/15 16:09

どういたしまして。解決されたようでよかったです???? 参考までに、containers/ と components/  とに、どのようにコンポーネントを類別していくか、について追記しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問