1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React + material-UIでwebアプリ開発練習

Last updated at Posted at 2019-08-18

あくまで個人のメモです。

Reactとmaterial-UIを使ったwebアプリ練習

Live Chat App with React Tutorial | React Hooks, Material UI, Socket.io, Node
を参考にしています(YouTube)。
というか、ここに書かれたコードをこの動画で説明されています。

#準備

$ npx create-react-app mui-test
$ npm install material-ui
$ npm install socket
$ npm install express

#コード

mui-test/public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta
     name="description"
     content="Web site created using create-react-app"
   />
   <link rel="apple-touch-icon" href="logo192.png" />
   <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
   <title>React App with Material-UI</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
mui-test/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
src/index.css
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
}
src/App.js
import React from 'react';
import './App.css';

import Dashboard from './Dashboard';
import Store from './Store';

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <Store>
          <Dashboard />
        </Store>
      </div>
    );
  }
}

export default App;
src/App.css
.App {
  text-align: center;
}
src/Dashboard.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Chip from '@material-ui/core/Chip';
//import Avatar from '@material-ui/core/Avatar';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';

import { CTX } from './Store';

const useStyles = makeStyles(theme => ({
  root: {
    margin: '50px',
    padding: theme.spacing(3, 2),
  },
  flex: {
    display: 'flex',
    alignItems: 'center',
  },
  topicsWindow: {
    width: '30%',
    height: '300px',
    borderRight: '1px solid grey',
  },
  chatWindow: {
    width: '70%',
    height: '300px',
    padding: '20px',
  },
  topicBox: {
    width: '30%',
    padding: '10px',
  },
  topicButton: {
    width: '15%',
  },
  userBox: {
    width: '20%',
    padding: '10px',
  },
  chatBox: {
    width: '65%',
    padding: '10px',
  },
  button: {
    width: '15%',
  },
}));

function Dashboard() {

  const classes = useStyles();

  // CTX store
  const {allChats, sendChatAction, addTopicAction} = React.useContext(CTX);
  const topics = Object.keys(allChats);

  // local state
  const [activeTopic, changeActiveTopic] = React.useState(topics[0]);
  const [textValue, changeTextValue] = React.useState('');
  const [userValue, changeUserValue] = React.useState('');
  const [addTopicValue, changeAddTopicValue] = React.useState('');

  return (
    <div>
      <Paper className={classes.root}>
        <Typography variant="h4" component="h4">
          メッセージアプリ
        </Typography>
        <Typography variant="h5" component="h5">
          {activeTopic}
        </Typography>
        <div className={classes.flex}>
          <div className={classes.topicsWindow}>
            <List>
              {
                topics.map(topic => (
                  <ListItem 
                    onClick={e => changeActiveTopic(e.target.innerText)}
                    key={topic} 
                    button
                  >
                    <ListItemText primary={topic} />
                  </ListItem>
                ))
              }
            </List>
          </div>
          <div className={classes.chatWindow}>
            {
              allChats[activeTopic].map((chat, i) => (
                <div className={classes.flex} key={i}>
                  <Chip
                    label={chat.from} 
                    className={classes.chip} 
                  />
                  <Typography 
                    variant="body1"
                    gutterBottom
                  >
                    {chat.msg}
                  </Typography>
                </div>
              ))
            }
          </div>
        </div>
        <div className={classes.flex}>
          <TextField
            label="add topic"
            className={classes.topicBox}
            value={addTopicValue}
            onChange={e => changeAddTopicValue(e.target.value)}
          />
          <Button
            variant="contained"
            color="primary"
            className={classes.topicButton}
            onClick={() => {
              addTopicAction(addTopicValue);
              changeAddTopicValue('');
            }}
          >
            add
          </Button>
        </div>
        <div className={classes.flex}>
          <TextField
            label="user name"
            className={classes.userBox}
            value={userValue}
            onChange={e => changeUserValue(e.target.value)}
          />
          <TextField
            label="Send a chat"
            className={classes.chatBox}
            value={textValue}
            onChange={e => changeTextValue(e.target.value)}
          />
          <Button 
            variant="contained"
            color="primary"
            className={classes.button}
            onClick={() => {
              sendChatAction({
                from: userValue, 
                msg: textValue, 
                topic: activeTopic
              });
              changeTextValue('');
              changeUserValue('');
            }}
          >
            Send
          </Button>
        </div>
      </Paper>
    </div>
  );
}

export default Dashboard;
src/Store.js
import React from 'react';
import io from 'socket.io-client';

export const CTX = React.createContext();

/*
  msg: {
    from: 'user',
    msg: 'message',
    topic: 'general'
  },

  state: {
    topic1: [msg, msg, msg, ...],
    topic2: [msg, msg, ...],
  }
*/


const initState = {
  general: [
  ],
}

function reducer(state, action) {
  const {from, msg, topic} = action.payload;

  switch(action.type) {
    case 'RECEIVE_MESSAGE': 
      return {
        ...state,
        [topic]: [
          ...state[topic],
          {from, msg, }
        ]
      }
    case 'ADD_TOPIC': 
      return {
        ...state,
        [topic]: [],
      }
    default:
      return state;
  }
}

let socket;

function sendChatAction(value) {
  socket.emit('chat message', value);
}

function addTopicAction(value) {
  socket.emit('add topic', value);
}

function Store(props) {

  const [allChats, dispatch] = React.useReducer(reducer, initState);
  
  if(!socket) {
    socket = io(':3001');
    socket.on('chat message', msg => {
      dispatch({type: 'RECEIVE_MESSAGE', payload: msg});
    });
    socket.on('add topic', topic => {
      if (!allChats[topic]) {
        dispatch({type: 'ADD_TOPIC', payload: {from:'', msg:'', topic}});
      }
    })
  }

  return (
    <CTX.Provider value={{allChats, sendChatAction, addTopicAction}}>
      {props.children}
    </CTX.Provider>
  );
}

export default Store;

#サーバー

src/server/index.js
var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);

app.get('/', (req, res) => {
  res.send('<h1>Hello</h1>');
});

io.on('connection', socket => {
  console.log('a user connected');
  socket.on('chat message', msg => {
    console.log('message: ' + JSON.stringify(msg));
    io.emit('chat message', msg);
  });
  socket.on('add topic', topic => {
    console.log('topic: ' + topic);
    io.emit('add topic', topic);
  })
});

http.listen(3001, () => {
  console.log('listening on *:3001');
});

起動

$ node index.js

#まとめ
material-ui使うときれいな見た目になる。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?