Repository URL to install this package:
|
Version:
2.2.4 ▾
|
| helpers |
| middlewares |
| state |
| containers |
| state |
| README.md |
| index.js |
| package.json |
| styles.dev.css |
Connects Doodle's users API to redux. For interactions with the API, this package provides a set of:
that can be connected to the react-redux state through createStore.
💡 A connector is meant to provide a plug'n'play way to integrate data from a service API in your redux state.
As most Doodle frontends may contain some UI (like the user avatar in the header) related to the data for a logged-in user, this connector aims at to provide easily reusable state management for React frontends with redux state management.
loadUser() action to trigger cookie detection, token exchange and user profile fetching/api/v2.0/users/me endpoint with tokenuser.data.profile and user.loading stateSee web-example.
Add the private registry:
echo "registry=https://npm-proxy.fury.io/mfsTqYdDz3bsKFQJuMAR/tmf/" >> .npmrc
🚧 todo switch to nexus.doodle.com's npm registry
Add the package to your package.json:
yarn add --save --exact @doodle/users-api-connector
Let's get started with a minimal example which supports:
mkdir connector-example cd connector-example npm init -y echo "registry=https://npm-proxy.fury.io/mfsTqYdDz3bsKFQJuMAR/tmf/" >> .npmrc npm i -S @doodle/users-api-connector redux redux-saga react react-redux react-dom express express-http-proxy webpack html-webpack-plugin babel-preset-react babel-loader babel-core babel-plugin-transform-object-rest-spread
Add a store.js file:
// store.js import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; import createSagaMiddleware, { END } from 'redux-saga'; import { all, call } from 'redux-saga/effects'; import { createReducer as createUsersApiReducer, createState as createUsersApiInitialState, loadUserSaga, } from '@doodle/users-api-connector'; const sagaMiddleware = createSagaMiddleware(); const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; const middlewares = [ sagaMiddleware, // ... other redux middlewares ]; const enhancers = composeEnhancers( applyMiddleware(...middlewares) // ... other enhancers ); function* rootSaga(options = {}) { yield all([ call(loadUserSaga, options.usersApi), // ... other sagas ]); } const rootReducer = combineReducers({ ...createUsersApiReducer(), // ... other reducers }); // load initial state through SSR or with the connector's local storage as a falback const initialState = (typeof window !== 'undefined' && window.__SSR_STATE__) || { ...createUsersApiInitialState(), // ... other initial states }; export default () => { const store = createStore(rootReducer, initialState, enhancers); store.runRootSaga = options => sagaMiddleware.run(rootSaga, options); store.close = () => store.dispatch(END); return store; };
As an example, add a simple container in UserProfile.js, which is connected to the user state:
// UserProfile.js import React from 'react'; import { connect } from 'react-redux'; const Profile = ({ name, loading }) => ( <span style={{ fontStyle: loading ? 'italic' : 'normal' }}>{name ? `${name}${loading ? '...' : ''}` : 'Login'}</span> ); const mapStateToProps = state => ({ name: state.user.data.profile && state.user.data.profile.name, loading: state.user.loading, }); export const UserProfile = connect(mapStateToProps)(Profile);
Client-side rendering
// client.js import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import createStore from './store'; import { UserProfile } from './UserProfile'; const store = createStore(); store.runRootSaga({ usersApi: { url: '/api/v2.0/users', // using the API proxy below, avoiding CORS }, }); ReactDOM.render( <Provider store={store}> <UserProfile /> </Provider>, document.getElementById('app') );
Server-side rendering:
// server.js import express from 'express'; import proxy from 'express-http-proxy'; import React from 'react'; import { renderToString } from 'react-dom/server'; import { Provider } from 'react-redux'; import createStore from './store'; import { UserProfile } from './UserProfile'; // create express app const app = express(); // simple html templating const template = (html, state) => `<!doctype html> <html> <head><title>SSR</title></head> <body> <div id="app">${html}</div> <script> window.__SSR_STATE__ = ${JSON.stringify(state)}; </script> <script src="/public/client-bundle.js" async></script> </body> </html> `; // API proxy, avoiding CORS app.use( '/api/v2.0/users', proxy('beta.doodle.com', { https: true, proxyReqPathResolver: req => req.originalUrl, }) ); app.use('/public', express.static(`${process.cwd()}/dist/`)); // react server-side rendered pages app.use('/', (req, res) => { const store = createStore(); // kick off root saga: the @doodle/users-api-connector loadUserSaga bootstraps the `loadUser` action const saga = store.runRootSaga({ usersApi: { getCookie: () => req.headers.cookie, url: 'https://beta.doodle.com/api/v2.0/users/', }, }); // dispatch the redux END action: current operations will resolve, watchers are terminated store.close(); // after all pending sagas are terminated, render component with current store saga.done.then(() => res.send( template( renderToString( <Provider store={store}> <UserProfile /> </Provider> ), store.getState() ) ) ); }); // start app app.listen(3000);
// webpack.config.js const webpack = require('webpack'); const nodeExternals = require('webpack-node-externals'); const rules = [ { test: /\.js/, loader: 'babel-loader', options: { presets: ['react'], plugins: ['transform-object-rest-spread'], }, }, ]; const plugins = [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production'), }, }), ]; module.exports = [ { entry: './client.js', output: { filename: 'client-bundle.js', path: `${__dirname}/dist`, publicPath: '/public/', }, plugins: plugins, module: { rules: rules, }, }, { entry: './server.js', output: { filename: 'server-bundle.js', path: `${__dirname}/dist`, }, target: 'node', externals: nodeExternals(), plugins: plugins, module: { rules: rules, }, }, ];
Compile & run
./node_modules/.bin/webpack node ./dist/server-bundle.js & open http://localhost:3000
Login to private npm registry:
npm login #Username: tmf #Password: #Email: (this IS public) tmf@doodle.com
After bumping version in package.json, publish new version:
npm publish
Creating link
yarn link
yarn link "@doodle/users-api-connector"
resolve: { alias: { 'react-redux': path.resolve('./node_modules/react-redux'), 'react-dom': path.resolve('./node_modules/react-dom'), }, },
yarn unlink
and:
yarn unlink "@doodle/users-api-connector" yarn
to unlink a package that was symlinked during development in your project,
🔮 improvement switch to semantic versioning?