Redux-saga preload data on route change - redux

I want to load a set of data from an api using redux-saga but I can't find an example of how to do this when navigating to a new route (eg /posts) before rendering the route.
How would I do this?

You can use the onEnter prop on Route:
<Route path='posts' onEnter={() => store.dispatch({ type: 'FETCH_POSTS' })} />
In this case, you can't use the store in the context of the tree that is inserted in Provider. You'd have to import it and use directly.
Another option is instead of passing a component prop, pass a getComponent:
<Route path='posts' getComponent={(nextState, cb) => {
store.dispatch({ type: 'FETCH_POSTS' });
cb(null, PostList);
}} />
I can't tell what are the pros/cons for any of those approaches though since I've never used them. I just tried to find a solution that could work for your use case.

Just add a call to fetch data when the new component mounts. For this to work you need to write the component so that it can at least partially render with incomplete data.

Related

Passing arguments to 'loader'

I have a parent component A, creating a child component B.
This child component's definition is imported using Loadable. In the following, component B is the Catalog.
Now I want to fetch some initial data before rendering this component, as it is described in the doc. Here's what I have :
const Catalog = Loadable.Map({
loader: {
Catalog: () => import('./Catalog'),
initialData: () => fetchCatalogInitialData(foo1, foo2),
},
loading: () => {return <div>foo</div>},
render(loaded, props) {
let Catalog = loaded.Catalog.default;
let initialData = loaded.initialData;
return <Catalog {...props} initialData={initialData} />;
},
});
I'd like fetchCatalogInitialData to be called with parameters, i.e a URL, a token, etc. Is this doable in a nice way?
As far as I know, this is not doable without forking the library. There are a few rejected PRs (example) that attempt to do this that the maintainer has rejected. To be fair, the rationale the author provides is as follows:
Your loader should be as static as possible or it's impossible to do things like server-side rendering reliably. I know that it is very limiting, but it's on purpose.
If you're not doing SSR, forking may be the best solution.

Is it a bad idea to dispatch actions from mapDispatchToProps, and why?

We are using react-router-redux for routing in our app.
<Route path='/users/:userId' component={SomeComponent} />
In this way, SomeComponent will receive a userId in its match params. The problem is that in order for the children of SomeComponent to receive the id we must pass it as props from parent to child. As much as possible we try to avoid passing props from parent to child, preferring to use containers and the redux store.
In order to provide the params to children, we are considering having SomeComponent dispatch an action when its props change. A convenient way to do this is:
const mapDispatchToProps = (dispatch: Dispatch<IState>, props: IPropsFromParent): IPropsFromDispatch => {
dispatch(ActionCreators.updateShowingUserId({
userId: props.match.params.userId,
}))
return {
anotherAction: () => {
dispatch(ActionCreators.doOtherStuff())
}
}
}
This way, whenever the component receives a new userId from the router, it will update the value in the store. Components which render deeper in the tree but which are not themselves connected to the router can use this value for conditional rendering, etc...
However, we are concerned that this may be bad practice for reasons that we are not yet aware of. It is known that calling setState in a react component's render function is forbidden. I wonder if there is some similar concrete restriction on calling dispatch in mapDispatchToProps.
Is it a bad idea to dispatch actions in mapDispatchToProps, and if so, why?
Since the dispatch will be run every time the redux store was updated it (and the props of the component change) wouldn't be a good idea to dispatch something inside the mapStateToProps, mapDispatchToProps or mergeProps functions, since you would overwrite the previous value with a default one. You should add the value you're dispatching to the initial store when possible or dispatch the defaults from the parent which controls the component.
Documentation for ReactRedux.connect

React-redux: should the render always happen in the same tick as dispatching an action?

In my react-redux application, I have a controlled text input. Every time the component changes value, it dispatches an action and in the end, the value comes back through the redux loop and is rendered.
In the example below this works well, but in practice, I've run into an issue where the render happens asynchronously from the action dispatch and the input loses cursor position. To demonstrate the issue, I've added another input with a delay explicitly put in. Adding a space in the middle of a word causes the cursor to skip in the async input.
I have two theories about this and would like to know which one is true:
This should work, but I have a bug somewhere in my production application that causes the delay
The fact that it works in the simple example is just luck and react-redux doesn't guarantee that render would happen synchronously
Which one is right?
Working example:
http://jsbin.com/doponibisi/edit?html,js,output
const INITIAL_STATE = {
value: ""
};
const reducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SETVALUE':
return Object.assign({}, state, { value: action.payload.value });
default:
return state;
}
};
const View = ({
value,
onValueChange
}) => (
<div>
Sync: <input value={value} onChange={(e) => onValueChange(e.target.value)} /><br/>
Async: <input value={value} onChange={(e) => { const v = e.target.value; setTimeout(() => onValueChange(v), 0)}} />
</div>
);
const mapStateToProps = (state) => {
return {
value: state.value
};
}
const mapDispatchToProps = (dispatch) => {
return {
onValueChange: (value) => {
dispatch({
type: 'SETVALUE',
payload: {
value
}
})
}
};
};
const { connect } = ReactRedux;
const Component = connect(
mapStateToProps,
mapDispatchToProps
)(View);
const { createStore } = Redux;
const store = createStore(reducer);
ReactDOM.render(
<Component store={store} />,
document.getElementById('root')
);
EDIT: Clarifying question
Marco and Nathan have both correctly pointed out that this is a known issue in React that won't be fixed. If there is a setTimeout or other delay between onChange and setting the value, the cursor position will be lost.
However, the fact that setState just schedules an update is not enough to cause this bug to happen. In the Github issue that Marco linked, there is a comment:
Loosely speaking, setState is not deferring rendering, it's batching
updates and executing them immediately when the current React job has
finished, there will be no rendering frame in-between. So in a sense,
the operation is synchronous with respect to the current rendering
frame. setTimeout schedules it for another rendering frame.
This can be seen in JsBin example: the "sync" version also uses setState, but everything is working.
The open question still is: is there something inside of Redux that creates a delay that lets a rendering frame in-between, or could Redux be used in a way that avoids those delays?
Workarounds for the issue at hand are not needed, I found one that works in my case but I'm interested in finding out the answer to the more general question.
EDIT: issue solved
I was happy with Clarks answer and even awarded the bounty, but it turns out it was wrong when I really tested it by removing all middlewares. I also found the github issue that is related to this.
https://github.com/reactjs/react-redux/issues/525
The answer is:
this is an issue in react-redux that will be fixed with react-redux 5.1 and react v16
What middleware are you using in your Redux application? Perhaps one of them is wrapping a promise around your action dispatches. Using Redux without middleware does not exhibit this behaviour, so I think it's probably something specific to your setup.
The issue is not related to Redux, but to React. It is a known issue and won't be fixed in the React core as it is not considered a bug but an "unsupported feature".
This answer explains the scenario perfectly.
Some attempts to address this issue have been made, but as you might see, they all involve a wrapper component around the input, so it's a very nasty solution if you ask me.
Asynchronously updating without losing the position was never supported
--- Dan Abramov (gaearon)
The solution is to track the cursor position and use a ref inside componentDidUpdate() to place the cursor correctly.
Additional info:
When you set attributes in react, internally this happens:
node.setAttribute(attributeName, '' + value);
When you set value this way, the behavior is inconsistent:
Using setAttribute() to modify certain attributes, most notably value in XUL, works inconsistently, as the attribute specifies the default value.
--- https://developer.mozilla.org/en/docs/Web/API/Element/setAttribute
Regarding your question about whether rendering occurs synchronously, react's setState() is asynchronous and used internally by react-redux:
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains
--- https://facebook.github.io/react/docs/react-component.html#setstate
There is an internal joke in the team that React should have been called "Schedule" because React does not want to be fully "reactive".
--- https://facebook.github.io/react/contributing/design-principles.html#scheduling
I think react-redux and redux are totally irrelevant to your case, this is pure React behavior. React-redux eventually calls setState on your component, there's no magic.
The problem that your async setState creates rendering frame between the react rendering and browser native event is because the batch update mechanism only happens within React synthetic events handler and lifecycle methods. Can check this post for detail.

what does createContainer in Meteor using React do?

I'm working on the Meteor using React tutorial and trying to understand createContainer(). From reading here:
http://guide.meteor.com/v1.3/react.html#using-createContainer
I think that its a function defined in meteor/react-meteor-data that is used for data loading. In this specific case, it retrieving data from the Mini Mongo Database (named Task here). My question is, what does the second argument to createContainer do? (named App here). Thank you!
class App extends Component {
//...class definition
}
export default createContainer(() => {
return {
//Tasks is a Mongo.Collection
//returns the matching rows (documents)
//here we define the value for tasks member
tasks: Tasks.find({}, { sort: { createdAt: -1} }).fetch(),
};
}, App);
A component created with createContainer is a simple wrapper around your actual component, but it's powerful in that it handles Meteor's reactivity for you so you don't have to think about how to keep your everything up to date when your data changes (e.g. a subscription loads, ReactiveVar / Session var changes)
A React component is basically just a JavaScript function, it is called with a bunch of arguments (props) and it produces an output. React doesn't know if your data has changed unless you tell it so. The component created with createContainer will re-render when your reactive data changes and send a new set of props to your actual component.
The options for createContainer are a function that returns the reactive data you want, and the component you want to wrap. It's really simple, and the render function for createContainer is literally one line:
return <Component {...this.props} {...this.data} />;
It passes through any props you pass to the wrapped component, plus it adds the reactive data source you set up.
You can see the code for yourself here: https://github.com/meteor/react-packages/blob/devel/packages/react-meteor-data/createContainer.jsx
The <Component {...this.props} syntax is known as a splat and basically turns:
{
prop1: 'some value',
prop2: 'another value'
}
into:
<Component prop1='some value' prop2='another value />
(See: https://facebook.github.io/react/docs/jsx-spread.html)
Asking a coworker, this is the answer that I got:
createContainer's second argument is the class name that you want the data to be encapsulated in. It will then have "reactive data" because every time the data in the DB is changed, the class's props will change to include the new data.
Also, the createContainer() function should be called outside of the class definition.
If anyone has anything to add please feel free to contribute.
createContainer's second argument is the name of the class in which you want to pass on the props to.
Lets say createContainer returns a prop called firstName
Now, whenever there is a new firstName entry or an updated firstName in the db, then createContainer is going to call the second argument which is our class name with the prop that it holds i.e firstName.
I hope that makes sense.

How to Two-way Data Binding Between Parents and grandchildren in Vue.js

I faced a problem, I solve it by cookies but I want to solve the problem without cookies. I have a component which called app-header and It has another component which called outmodal.
Now, My first Vue instance require component app-header.
var vue = new Vue({
el : "html",
data : {
title : "Site Title",
description : "description of page",
keywords : "my keywords",
view : "home",
login : "login"
},
components:{
"app-header" :require("../../components/header"),
"app-footer" :require("../../components/footer"),
"home" :require("../../views/home")
},
});
code of app-header
var Vue = require("vue");
Vue.partial("login",require("../../partials/login.html"));
Vue.partial("logged",require("../../partials/logged.html"));
module.exports = {
template : require("./template.html"),
replace : true,
components : {
outmodal : require("../outmodal")
},
props : ['login']
}
code of outmodal
var Vue = require("vue");
Vue.partial("loginModal",require("../../partials/loginModal.html"));
module.exports = {
template : require("./template.html"),
replace : true,
props : ['name'],
data : function () {
return {
userLogin : { mail : "", password : "", remember : ""}
}
},
methods : {
formSubmit : function(e){
e.preventDefault();
this.$http.post("http://example.com/auth/login",{ "email": this.userLogin.mail , "password": this.userLogin.password },function(data,status,request){
$.cookie("site_token",data.token,{expires : 1})
}).error(function(data,status,request){
});
}
}, ready : function(){
console.log("it works")
}
}
In outmodal component I connect the API and I check the login, If login will be succesfull, I want to change value of login variable in my Vue instance. I use web pack to build all requires. So I don't know how can I data binding between these files.
How can I solve It? I
The Best Solution which I found
For 0.12
http://012.vuejs.org/guide/components.html#Inheriting_Parent_Scope
for 1.0
http://v1.vuejs.org/guide/components.html#Parent-Child-Communication
for 2.0
https://v2.vuejs.org/v2/guide/components.html#Composing-Components (use props to one-way bind data from parent to child)
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.
i found this one to be more accurate.
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
only in 2.3.0+ tho.
and honestly it's still not good enough. should simply be a easy option for 'two-way' data binding. so none of these options is good.
try using vuex instead. they have more options for such purpose.
https://vuex.vuejs.org/en/state.html
I would prefer event-driven updates as recommended in the documentation. However, I was limited by the existing ("third-party") component already using props and $emit. This component is my grandchild. The following is my solution (passing value through child using props, sync and computed value with $emit.
Comments are welcome.
Value can be modified in parent and grandchild without error:
Grandchild (simplified third-party component):
<template>
<div v-show="value">{{ value}}</div>
<button #click="closeBox">Close</button>
</template>
<script>
export default {
props: {
value: null
},
methods: {
closeBox() {
this.$emit('update:value', null);
}
}
}
</script>
Child:
<template>
<grandchild-component :value.sync="passedValue" />
</template>
<script>
export default {
props: {
value: null
},
computed: {
passedValue: {
get() {
return this.value;
},
set(newVal) {
this.$emit('update:value', newVal);
}
}
}
}
</script>
Parent:
<template>
<child-component :value.sync="value" />
</template>
<script>
export default {
data() {
return {
value: null,
}
},
// ... e.g. method setting/modifying the value
}
</script>

Resources