あくまで個人のメモです。
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使うときれいな見た目になる。