Repository URL to install this package:
|
Version:
1.5.1-5b620ce ▾
|
| containers |
| helpers |
| middlewares |
| state |
| package.json |
| index.js |
| README.md |
| 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 fetchingDoodleIdentification and DoodleAuthentication cookie and get a token/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?