Skip to content

Instantly share code, notes, and snippets.

@ktec
Last active March 29, 2019 01:15
Show Gist options
  • Save ktec/58225384aa0222b496713aebb3fe0af3 to your computer and use it in GitHub Desktop.
Save ktec/58225384aa0222b496713aebb3fe0af3 to your computer and use it in GitHub Desktop.
HOW TO USE PHOENIX WITH WEBPACK + REACT + REDUX - This is a collection of small changes related to: https://www.dailydrip.com/blog/how-to-use-phoenix-with-webpack-react-redux.html
export const SEND_MESSAGE = 'SEND_MESSAGE';
function sendMessage(message) {
return {
type: SEND_MESSAGE,
payload: {
message
}
};
}
export default { sendMessage };
import React, { Component } from "react";
import { connect } from "react-redux";
import MessageList from "../components/MessageList";
import MessageInput from "../components/MessageInput";
import Actions from "../actions";
function App(props) {
const { messages, sendMessage } = props;
return (
<div>
<MessageList messages={messages} />
<MessageInput onClick={sendMessage} />
</div>
);
}
export const AppContainer = connect(
function mapStateToProps(state) {
return {
messages: state.mainReducer.get("messages")
};
},
function mapDispatchToProps(dispatch) {
return {
sendMessage: message => {
dispatch(Actions.sendMessage(message));
}
};
}
)(App);
export default AppContainer;
import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import App from "./containers/App";
import MyStore from "./store";
render(
<Provider store={MyStore}>
<App />
</Provider>,
document.getElementById("root")
);
defmodule XxxWeb.LayoutView do
use XxxWeb, :view
def js_script_tag do
# This assumes config is setup
if Application.get(:xxx, :webpack_dev_server) == :enabled do
# In development mode we'll load it from our webpack dev server
"<script src=\"http://localhost:8080/js/app.js\"></script>"
else
# In production we'll just reference the file
"<script src=\"/js/index.js\"></script>"
end
end
# Ditto for the css
def css_link_tag do
if Application.get(:xxx, :webpack_dev_server) == :enabled do
"<link rel=\"stylesheet\" type=\"text/css\" href=\"http://localhost:8080/css/app.css\" />"
else
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/app.css\" />"
end
end
end
import React from "react";
function MessageInput(props) {
const { onClick } = props;
let inputRef;
return (
<div>
<input type="text" ref={node => inputRef = node} />
<button onClick={() => onClick(inputRef.value)}>
SEND
</button>
</div>
)
}
export default MessageInput;
import React from "react";
function MessageList(props) {
const { messages } = props;
const renderMessage = (message, key) => {
return <div key={key}>{message.text}</div>;
}
const renderMessages = messages.map(renderMessage);
return <div className="message-list">{renderMessages}</div>;
}
export default MessageList;
{
"name": "xxx",
"version": "0.1.0",
"description": "xxx",
"main": "js/index.js",
"private": true,
"dependencies": {
"copy-webpack-plugin": "^4.1.1",
"immutable": "^3.8.2",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-immutable": "^4.0.0",
"redux-thunk": "^2.2.0",
"webpack": "^3.8.0",
"webpack-dev-server": "^2.9.2"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"css-loader": "^0.28.7",
"extract-text-webpack-plugin": "^3.0.1",
"import-glob-loader": "^1.1.0",
"node-sass": "^4.5.3",
"sass-loader": "^6.0.6",
"style-loader": "^0.19.0"
},
"scripts": {
"webpack": "webpack-dev-server --watch --color"
}
}
// @flow
import { List } from "immutable";
import { combineReducers } from "redux";
import { Model, MessageType } from "./types";
import { SEND_MESSAGE } from './actions';
const init = new Model();
type ActionType = SEND_MESSAGE;
function mainReducer(
model: Model = init,
action: { type: ActionType, payload: Object }
) {
switch (action.type) {
case SEND_MESSAGE:
return sendMessage(model, action.payload);
default:
return model;
}
}
function nullReducer(
model: Model = init,
action
) {
switch (action.type) {
default:
return model;
}
}
// private function!!
function sendMessage(model, payload) {
if (payload) {
const message = new MessageType({ text: payload.message })
const new_model = model.updateIn(
["messages"],
messages => messages.push(message)
);
return new_model;
} else {
return model;
}
}
const app = combineReducers({
mainReducer,
nullReducer
});
export default app;
// @flow
import reducers from "./reducers";
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
function composeWithApplyMiddlewares() {
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
return compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__()
);
}
return compose(applyMiddleware(thunk));
}
const createMyStore = () => {
return createStore(
reducers,
{},
composeWithApplyMiddlewares()
);
};
export default createMyStore();
var webpack = require("webpack");
var path = require("path");
// We'll be using the ExtractTextPlugin to extract any required CSS into a
// single CSS file
const ExtractTextPlugin = require("extract-text-webpack-plugin");
// We'll use CopyWebpackPlugin to copy over static assets like images and
// fonts
const CopyWebpackPlugin = require("copy-webpack-plugin");
var env = process.env.MIX_ENV || "dev";
var isProduction = env === "prod";
// We'll set up some paths for our generated files and our development server
const staticDir = path.join(__dirname, ".");
const destDir = path.join(__dirname, "../priv/static");
const publicPath = "/";
module.exports = {
entry: [
staticDir + "/js/index.js",
staticDir + "/css/app.scss"
],
output: {
path: destDir,
filename: "js/app.js",
publicPath
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: [
["es2015"],
["react"],
[
"env",
{
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
},
"include": ["transform-es2015-arrow-functions", "es6.map"],
"exclude": []
}
]
],
plugins: [
["transform-object-rest-spread", { "useBuiltIns": true }],
["transform-class-properties", { "spec": true }]
]
}
},
// Any CSS or SCSS files will run through the css loader, the sass
// loader, and the import-glob-loader. The last one will allow us to use
// glob patterns to import SCSS files - for instance, a whole directory of
// them. That isn't available by default in node-sass
{
test: /\.s?css$/,
exclude: [path.resolve(__dirname, "node_modules")],
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader', 'import-glob-loader']
})
}
]
},
// And we'll configure our ExtractTextPlugin and CopyWebpackPlugin
plugins: [
new ExtractTextPlugin("css/app.css"),
// We copy our images and fonts to the output folder
new CopyWebpackPlugin([{ from: "./static/images", to: "images" }])
]
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment