I am starting on a new project and I want to keep my code as structured as possible. I was planning on using the flux pattern for the front-end but it feels like the event driven process that flux follows, goes against the grain of the reactionary way that meteor handles data and view updates.
Here is an example of what one of my stores might look like using flux and meteor
import { EventEmitter } from 'events'
import appDispatcher from '../Dispatcher'
import Meteor from 'meteor/meteor'
class TaskStore extends EventEmitter {
constructor() {
super();
this.tasks = [];
Meteor.subscribe('tasks');
}
addTask(task) {
Meteor.addTask(task)
this.tasks = Meteor.findAll();
}
getTasks() {
return this.tasks
}
}
const taskStore = new TaskStore();
appDispatcher.register((payload) => {
switch (payload.actionName) {
case 'CLICK':
taskStore.addTask(payload.newItem.action);
taskStore.emit('ADD_TASK');
break;
default:
}
});
export default taskStore
It's pretty straight forward, the store responds to the dispatcher by adding a task to the mongo database, then updates the local model with the data from the database and emits a change event. The view would respond by calling the getTasks() method and updating the state.
This works, but it doesn't feel very reactionary. I need a separate method exposed for finding all the tasks for example, where as in the documentation for react views that meteor provides, they have their own special function that wraps components and updates the props of the component whenever the data changes
export default createContainer(() => {
Meteor.subscribe('tasks');
return {
tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
incompleteCount: Tasks.find({ checked: { $ne: true } }).count()
})
This seems to be the way that meteor was designed. Views react to data changes and update in real time across all platforms, and I'm just not certain if my implementation of the flux pattern is the best way to stay true to that design, or if I should even bother trying to stay true to that design at all.
As a disclaimer, I'm still extremely new to both the flux pattern, and the Meteor framework.
Related
I am attempting to learn VueJS and I'm finding it hard to understand how to make models and collections.
For example, I want to have a collection (or list) of Employees and each Employee is a model.
But I'm not sure how to accomplish this in VueJS
Many thanks
Vue was initially created to bind data to a template in a reactive way, therefore, there's no "controller" or "model" notion like you would have in a regular MVC.
Vue just needs plain javascript objects as data, so if some of your data needs to be mapped to a model, well it's not about Vue, it's about... Javascript.
Here is an example of implementation (in ES6) :
class UserModel {
constructor(data) {
this.data = data
}
name() {
return this.data.firstname + ' ' + this.data.lastname
}
// and so on, put other methods here
}
var app = new Vue({
el: '#app',
data: {
user: {}
},
created() {
// axios or anything else for your ajax, or a new fetch api
axios.get('/me')
.then(response => {
// of course the "this" here is the Vue instance because i used an es6 arrow function
this.user = new UserModel(response.data)
})
}
})
That's it.
As you can see, Vue doesn't have to do with the Model class I created, I just mapped my ajax data to a custom class, then mapped the result to the Vue instance.
As simple as that.
Gee, I feel foolish about this, but I have read every part of: http://redux.js.org/ (done the egghead tutorials, and read 4 times the FAQ at: http://redux.js.org/docs/faq/ImmutableData.html
What I did was stub one of my reducers, to always return state, and that is the only reducer being called (checked with breakpoints). Even so, my subscribe event is being called every time the reducer returns state. What Do I not understand? (Action.SetServerStats is being called at a 1Hz rate, and the subscribe is also being called at a 1Hz Rate
BTW the Chrome Redux Extension says thats states are equal, and the React Extension for Chrome with Trace React Updates, is not showing any updates.
I will be glad to remove the question, when someone clues me in. But right now, what I see each each of the reducers being called at 1Hz, and all of them returning the slice of the store that they got (state).
So do I not understand subscribe, and that it returns every time even when the store tree does not get modified (and it is up to react-redux to do shallow compare to figure out what changed if any?)
create store & subscribe
let store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)))
store.subscribe(() => console.log("current store: ", JSON.stringify(store.getState(), null, 4)))
reducers.js
import A from './actionTypes'
import { combineReducers } from 'redux'
export const GLVersion = (state = '', action) => {
switch (action.type) {
case A.SetGLVersion:
return action.payload
default:
return state
}
}
export const ServerConfig = (state = {}, action) => {
switch (action.type) {
case A.SetServerConfig: {
let { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath } = action.payload
let p = { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath }
return p
}
default:
return state
}
}
export const ServerStats = (state = {}, action) => {
switch (action.type) {
case A.SetServerStats:
return state
// let { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize } = action.payload
// let s = { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize }
// return s
default:
return state
}
}
export default combineReducers({ GLVersion, ServerConfig, ServerStats })
Correct. Redux will execute all subscription callbacks every time an action is dispatched, even if the state is not updated in any way. It is up to the subscription callbacks to then do something meaningful, such as calling getState() and checking to see if some specific part of the state has changed.
React-Redux is an example of that. Each instance of a connected component class is a separate subscriber to the store. Every time an action is dispatched, all of the wrapper components generated by connect will first check to see if the root state value has changed, and if so, run the mapStateToProps functions they were given to see if the output of mapState has changed at all. If that mapState output changes, then the wrapper component will re-render your "real" component.
You might want to read my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance, which discusses several important aspects related to Redux performance. My new post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent also goes into detail on how several parts of Redux actually work.
How can one persist the full router history of a user visiting an SSR react-redux app? I have tried modifying the react-redux-router package's reducer.js file as such...but when the user loads via SSR, the history array is reset.
/**
* This action type will be dispatched when your history
* receives a location change.
*/
export const LOCATION_CHANGE = '##router/LOCATION_CHANGE'
const initialState = {
locationBeforeTransitions: null,
locationHistory: []
}
/**
* This reducer will update the state with the most recent location history
* has transitioned to. This may not be in sync with the router, particularly
* if you have asynchronously-loaded routes, so reading from and relying on
* this state is discouraged.
*/
export function routerReducer(state = initialState, { type, payload } = {}) {
if (type === LOCATION_CHANGE) {
return { ...state,
locationBeforeTransitions: payload,
locationHistory: state.locationHistory.concat([payload]) }
}
return state
}
ref: https://github.com/reactjs/react-router-redux/blob/master/src/reducer.js
However, I think this is supposed to be achieved in a middleware.
Irregardless, this (storing the entire previous session history) seems like a common enough use case that perhaps someone has already formulated a best practice.??
Perhaps even this full history is accessible via the historyjs object in react-router w/o react-router-redux.
I'm looking for answers to how to fulfill storing the full history of a user's session in the redux state and post it to my api server when the user closes the browser or navigates away from the site. (if this is not possible, i could just post it upon every navigation.) Then I would like to show this history in a 'recently viewed' list of pages on the users' home pages.
First of all, you don't have to meddle with the internals of react-redux-router.
As you can see in the code you presented, react-redux-router exports a LOCATION_CHANGE action.
You can use this action in a reducer of your own. Here's an example:
// locationHistoryReducer.js
import { LOCATION_CHANGE } from 'react-router-redux';
export default function locationHistory(state = [], action) {
if (action.type === LOCATION_CHANGE) {
return state.concat([action.payload]);
}
return state;
}
However, this may be unnecessary. Your assumption that this can be be achieved with middleware is correct. Here's an example of a middleware layer:
const historySaver = store => next => action => {
if (action.type === LOCATION_CHANGE) {
// Do whatever you wish with action.payload
// Send it an HTTP request to the server, save it in a cookie, localStorage, etc.
}
return next(action)
}
And here's how to apply that layer in the store:
let store = createStore(
combineReducers(reducers),
applyMiddleware(
historySaver
)
)
Now, how you save and load data is entirely up to you (and has nothing to do with react-router and the browser's history).
In the official docs, they recommend injecting the initial state on the server side using a window.__PRELOADED_STATE__ variable.
Is this possible?
I'm using redux store in an IoC environment and want to add middleware to the store after it is created.
e.g.:
class MyApp {
store = createStore(...);
}
let app = new MyApp();
// later on
import thunk from 'redux-thunk';
app.store.addEnhancer(thunk);
I have created a function to do this. If redux think this is valuable, I can do a PR.
This is code that tailored to my module. The actual one add to PR will look a bit different.
addMiddleware(middleware: Middleware) {
const middlewareAPI: MiddlewareAPI<any> = {
getState: this.getState,
dispatch: (action) => this.dispatch(action)
};
this.dispatch = compose(middleware(middlewareAPI))(this.dispatch);
}
I was able to use the code from above from unional but I needed to change it slightly to get it working with 2018 redux.
constructor(myMiddleware: myMiddleware) {
this.addMiddleware(myMiddleware)
}
addMiddleware(middleware: Middleware) {
this.redux.dispatch = compose(applyMiddleware(middleware))(this.redux.dispatch);
}
I'm trying to build keyboard shortcut support into my React/Redux app in an idiomatic React/Redux way. The way I am planning to do this is to have the following action creator and associated action:
registerShortcut(keyCode, actionCreatorFuncReference)
The reducer would then update a registeredShortcuts object in the redux store with a mapping of keyCodes to actionCreatorFuncReferences. Then my root component would listen for keyup and see if there is an associated keyCode registered and if so, then dispatch the mapped action via the action creator function reference.
However, this would be the first time I am storing function references in my Redux store. To date, I've only had objects with keys with vanilla values (strings, ints, etc).
The Redux docs says:
You should do your best to keep the state serializable. Don’t put anything inside it that you can’t easily turn into JSON.
Does this suggest it's a bad idea to store such function references in my Redux store? If so, what is a better way to accomplish what I'm trying to do in React/Redux?
An alternative approach is just to store the mapping of keyCodes and function references in the root react component itself, but that didn't feel very Redux-like since now the application state is not in the Redux store.
No, you should not store function references in the redux store. They are not serializable, and as you mentioned state should be serializable at all times. The most redux-friendly approach I can think of is just to keep the map of hotkeys to their actionCreatorFuncNames.
TL;DR: You don't. The store state must be serializable at all times (as Nathan answered).
The Redux way is via enhancers, or the Redux-Observable way via dependencies.
NL;PR: Based on the Redux docs example, what you want is to pass the reference in your action(1), ignore it your reducer(2) and use it in your enhancer(3):
//... in your action:
const data={val:1}, ref=()=>{};
const action = {type:'ACTION_WITH_REF', data, ref}; //(1)
//... in your reducer:
case 'ACTION_WITH_REF':
return {...state, data: action.data}; //(2)
//... and in your enhancer:
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
export const myRefStore= {};
function refHandler({ getState }) {
return next => action => {
switch(action.type){
// this can be done more elegantly with a redux-observable
case 'ACTION_WITH_REF':
myRefStore.aRef = action.ref; // (3)
break;
}
// be sure to maintain the chain of the store
const returnValue = next(action);
// otherwise, your midddeware will break the store
return returnValue;
};
}
const store = createStore(
reducers,
initialState,
applyMiddleware(refHandler)
);
Note: As far as there are no side-effects in your enhancers, you are good to go. Be aware that you could have obtained the refs directly in the reducers, but such an approach keeps the reference at the reducer-level and misses the point of combineReducers(). With an enhancer, you keep them all in one place(myRefStore).
One final observation is that a redux store is not an any-data store but a state store, thus why we need to handle functions and other non-state related stuff in enhancers. You can leverage the enhancer backbone to Redux-Observable and inject myRefStore via dependencies.
I'm new to redux, but the way I see it, you could pass the key code and an action type.
Then a reducer could be listening for that action type and make changes accordingly.
Here is an example using the library Mousetrap:
// On your Container
function registerShortcut(element, dispatch, keyCode, actionType) {
Mousetrap(element).bind(keyCode, function(e) {
dispatch({
type: actionType,
payload: {
keyCode: keyCode,
event: e
}
});
});
});
mapDispatchToProps = function(dispatch) {
return {
onMount: function(element) {
registerShortcut(element, dispatch, ['command+f', 'ctrl+f'], 'OPEN_SEARCH');
},
onUnmount: function(element) {
Mousetrap(element).unbind(['command+f', 'ctrl+f']);
}
};
};
// On your Component
componentDidMount() {
onMount(ReactDOM.findDOMNode(this));
};
componentWillUnmount() {
onUnmount(ReactDOM.findDOMNode(this));
};
// On your reducer
function reducer(oldState, action) {
if (action.type == 'OPEN_SEARCH') {
//... make changes ...//
return newState;
}
return oldState;
};
This way, keyboard shortcuts will dispatch an action. The reducer will make the changes necessary to the state. And finally, the application can re-render.