I am trying to implement a search filter in my application which uses react/redux using redux-search. The first gotcha I get is when I try to add the store enhancer as in the example.
// Compose :reduxSearch with other store enhancers
const enhancer = compose(
applyMiddleware(...yourMiddleware),
reduxSearch({
// Configure redux-search by telling it which resources to index for searching
resourceIndexes: {
// In this example Books will be searchable by :title and :author
books: ['author', 'title']
},
// This selector is responsible for returning each collection of searchable resources
resourceSelector: (resourceName, state) => {
// In our example, all resources are stored in the state under a :resources Map
// For example "books" are stored under state.resources.books
return state.resources.get(resourceName)
}
})
)
I understand evarything up to the resourceSelector, when I tried to get a deep dive into the example to see how it works but I can barely see how they are generated and the last line returns an error, Cannot read property 'get' of undefined
My state object looks like this
state: {
//books is an array of objects...each object represents a book
books:[
//a book has these properties
{name, id, author, datePublished}
]
}
Any help from anyone who understands redux-search is helpful
If this line:
return state.resources.get(resourceName)
Is causing this error:
Cannot read property 'get' of undefined
That indicates that state.resources is not defined. And sure enough, your state doesn't define a resources attribute.
The examples were written with the idea in mind of using redux-search to index many types of resources, eg:
state: {
resources: {
books: [...],
authors: [...],
// etc
}
}
The solution to the issue you've reported would be to either:
A: Add an intermediary resources object (if you think you might want to index other things in the future and you like that organization).
B: Replace state.resources.get(resourceName) with state[resourceName] or similar.
Related
I have a question which is a mix of both composition API and options API
What I want to do: I want to watch an object. That object is deeply nested with all kinds of data types.
Whenever any of the nested properties inside change, I want the watch to be triggered.
(This can be done using the deep: true option).
AND I want to be able to see the previous value and current value of the object.
(this doesn't seem to be possible because Vue stores the references of the objects, so, now the value and prevValue point to the same thing.)
In Vue3 docs, for the watch API, it says this
However, watching a reactive object or array will always return a reference to the
current value of that object for both the current and previous value of the state.
To fully watch deeply nested objects and arrays, a deep copy of values may be required.
This can be achieved with a utility such as lodash.cloneDeep
And this following example is given
import _ from 'lodash'
const state = reactive({
id: 1,
attributes: {
name: ''
}
})
watch(
() => _.cloneDeep(state),
(state, prevState) => {
console.log(state.attributes.name, prevState.attributes.name)
}
)
state.attributes.name = 'Alex' // Logs: "Alex" ""
Link to docs here - https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watching-reactive-objects
However, this is composition API (if I'm not wrong).
How do I use this way of using cloneDeep in a watch defined in options API?
As an example, this is my code
watch: {
items: {
handler(value, prevValue) {
// check if value and prevValue are STRUCTURALLY EQUAL
let isEqual = this.checkIfStructurallyEqual(value, prevValue)
if (isEqual) return
else this.doSomething()
},
deep: true,
},
}
I'm using Vue 3 with Options API.
How would I go about doing this in Options API?
Any help would be appreciated! If there's another way of doing this then please do let me know!
I also asked this question on the Vue forums and it was answered.
We can use the same syntax as provided in the docs in Options API using this.$watch()
data() {
id: 1,
attributes: {
name: ''
}
}
this.$watch(
() => _.cloneDeep(this.attributes),
(state, prevState) => {
console.log(state.name, prevState.name)
}
)
this.attributes.name = 'Alex' // Logs: "Alex" ""
I have the following code:
import { Query } from 'react-apollo';
type Post = {
id: string
};
interface Data {
posts: Array<Post>;
}
class PostsQuery extends Query<Data> {}
When using the above as follows:
<PostsQuery query={POSTS_QUERY}>
{({ loading, data }) => {
...
{data.posts.map(...)}
...
}
</PostsQuery>
I get the following error from flow:
Error:(151, 27) Cannot get 'data.posts' because property 'posts' is missing in object type [1].
Any idea why?
I did use flow-typed to add apollo-client_v2.x.x.js to my project by the way
Solution to the problem
Continued from the answer explaining how to make a verifiable example and research the problem.
So it looks like this part of react-apollo isn't typed in such a way to make accessing the data contents straightforward. Okay, that's fine, we can take their recommendation on destructuring and check for empty data. At the same time, we can also add an id property to the Post type so flow stops complaining about that:
(Try - Scroll to bottom for relevant code)
type Post = {
id: string,
title: string;
};
...snip...
// Look ma, no errors
class Search extends React.Component<{}> {
render = () => (
<PostsQuery query={QUERY}>
{({ loading, error, data }) => {
if (error) {
return <p>Error</p>
}
if (loading) return <p>Loading</p>
const nonNullData = (data || {})
const dataWithAllPosts = {allPosts: [], ...nonNullData}
const {allPosts} = dataWithAllPosts
if (allPosts.length == 0) {
return <p>Empty response or something</p>
}
return (
<div>
{allPosts.map(post => {
return <div key={post.id}>{post.title}</div>;
})}
</div>
);
}}
</PostsQuery>
);
}
I'm not familiar with the react-apollo library, so I'm not sure how you want to handle that case where there are no posts. I just added a message as seen above. It's entirely possible that the case never occurs (again, you would know better than I do). If that's the case, you might want to skip some of the above steps and just assert the desired type with a cast through any.
How to make a reproducible example and research the problem
So the first thing we need to do while analyzing these types is to go lookup the typedefs in the flow-typed repo. I went ahead a copy-pasted the react-apollo typedefs into flow.org/try, modified them slightly (added an any somewhere, set gql to any), and was able to replicate your errors:
(Try - Scroll to the bottom for your code)
Referencing the relevant lines of the QueryRenderProps type, we can see why flow is throwing the error:
{
data: TData | {||} | void,
...
}
It looks like data can either be TData (probably what you want), an empty object, or undefined. Cross checking this with the typescript typings for react-apollo, we can see why the type is the way it is:
{
...
// we create an empty object to make checking for data
// easier for consumers (i.e. instead of data && data.user
// you can just check data.user) this also makes destructring
// easier (i.e. { data: { user } })
// however, this isn't realy possible with TypeScript that
// I'm aware of. So intead we enforce checking for data
// like so result.data!.user. This tells TS to use TData
// XXX is there a better way to do this?
data: TData | undefined;
...
}
Unfortunately, due to the extreme length of these links and stackoverflow's limit on answer lengths, I have to continue my answer in another answer. I guess this answer can serve as an explanation of how to start debugging the problem.
I need help designing a simple app which allows user to rate videos using a form. My state is composed by 2 reducers, one that holds data about all ratable videos (in a normalized fashion) and another one that holds the form state:
{
videos: {
'video1Id': { id: 'video1Id', title: 'Cat video', duration: 120, ... },
'video2Id': { ... },
...
},
rateForm: {
'videoId': 'video1Id'
'userComment: 'A nice video about cat'
'formSubmitted': false
...
}
}
Note that, inside rateForm, I reference the video id instead of the video object. Problem is, how can I retreive the whole video object from my rateForm reducer ?
I feel like I'm following the best practice of Redux design but I'm stuck at this really simple use case. Any help appreciated.
Thanks
One thing to remember, reducer should be AS SIMPLE AS POSSIBLE. Only doing atomic operations on reducer level. From what I can tell you trying to retrieve the whole video object in your reducer just doesn't sound right.
Depending on your needs, usually, you don't need to fetch the whole video object if you just want to comment on it or rate it. But if you are 100% sure you have to, A good place to do this is in your action. Using Redux-Thunk, you will have access to the whole state object before you return your thunk. Example
function doSomethingToVideo (videoId, something) {
return (dispatch, getState) => {
const video = getState().videos[videoId]
// Do what ever
return somethingElse
}
}
Reference: Redux author's answer on a similar matter.
Accessing Redux state in an action creator?
Let's say I have an initial state in my redux application that looks like this:
{
themeParks : []
}
And theme park objects are stored in a MongoDB or wherever like this:
{
ParkName : "Disney World",
NumberOfRides : 25
}
My app fetches (via ajax) and displays the theme parks on load, and lets me do CRUD operations to add / remove / edit theme parks.
My question is, what is the best point in the application to merge in new properties that I will need, that are related to UI changes and need to be included in the state? For example, at some point I need to add an "editing" boolean property to each theme park, so they look like this:
{
ParkName : "Disney World",
NumberOfRides : 25,
editing : false
}
This "editing" flag needs to be a property of each theme park object, to provide the ability to have multiple theme parks in an editable state while using the application, is that correct? Obviously I don't want or need to store this flag in my database schema as it only relates to UI operations.
My first guess would be to include such logic within my .then function after successful return of the data, like this, but I'm not sure:
let ajax = new AjaxHanlder();
let promise = ajax.DoGet('/path/to/api-endpoint');
promise.then((themeParks) => {
themeParks.map((themePark, i) => {
themePark.editing = false;
});
dispatch({type : LOAD_THEME_PARKS, payload : themeParks});
});
Also, is there a convention for providing a definition of the theme park object before it is loaded, since the initial state is an empty array and has no knowledge of it?
Thanks in advance for any insight on this, as I attempt to advance my knowledge of redux design patterns :-)
I recommend adding the editing property to your LOAD_THEME_PARKS reducer. For example, part of your LOAD_THEME_PARKS reducer could look like:
case LOAD_THEME_PARKS:
return {
...state,
themeParks: action.payload.map(park => park.editing = false)
};
I'm trying to work with associating documents in different collections (not embedded documents) and while there is an issue for that in Mongooose, I'm trying to work around it now by lazy loading the associated document with a virtual property as documented on the Mongoose website.
The problem is that the getter for a virtual takes a function as an argument and uses the return value for the virtual property. This is great when the virtual doesn't require any async calls to calculate it's value, but doesn't work when I need to make an async call to load the other document. Here's the sample code I'm working with:
TransactionSchema.virtual('notebook')
.get( function() { // <-- the return value of this function is used as the property value
Notebook.findById(this.notebookId, function(err, notebook) {
return notebook; // I can't use this value, since the outer function returns before we get to this code
})
// undefined is returned here as the properties value
});
This doesn't work since the function returns before the async call is finished. Is there a way I could use a flow control library to make this work, or could I modify the first function so that I pass the findById call to the getter instead of an anonymous function?
You can define a virtual method, for which you can define a callback.
Using your example:
TransactionSchema.method('getNotebook', function(cb) {
Notebook.findById(this.notebookId, function(err, notebook) {
cb(notebook);
})
});
And while the sole commenter appears to be one of those pedantic types, you also should not be afraid of embedding documents. Its one of mongos strong points from what I understand.
One uses the above code like so:
instance.getNotebook(function(nootebook){
// hey man, I have my notebook and stuff
});
While this addresses the broader problem rather than the specific question, I still thought it was worth submitting:
You can easily load an associated document from another collection (having a nearly identical result as defining a virtual) by using Mongoose's query populate function. Using the above example, this requires specifying the ref of the ObjectID in the Transaction schema (to point to the Notebook collection), then calling populate(NotebookId) while constructing the query. The linked Mongoose documentation addresses this pretty thoroughly.
I'm not familiar with Mongoose's history, but I'm guessing populate did not exist when these earlier answers were submitted.
Josh's approach works great for single document look-ups, but my situation was a little more complex. I needed to do a look-up on a nested property for an entire array of objects. For example, my model looked more like this:
var TransactionSchema = new Schema({
...
, notebooks: {type: [Notebook]}
});
var NotebookSchema = new Schema({
...
, authorName: String // this should not necessarily persist to db because it may get stale
, authorId: String
});
var AuthorSchema = new Schema({
firstName: String
, lastName: String
});
Then, in my application code (I'm using Express), when I get a Transaction, I want all of the notebooks with author last name's:
...
TransactionSchema.findById(someTransactionId, function(err, trans) {
...
if (trans) {
var authorIds = trans.notebooks.map(function(tx) {
return notebook.authorId;
});
Author.find({_id: {$in: authorIds}, [], function(err2, authors) {
for (var a in authors) {
for (var n in trans.notebooks {
if (authors[a].id == trans.notebooks[n].authorId) {
trans.notebooks[n].authorLastName = authors[a].lastName;
break;
}
}
}
...
});
This seems wildly inefficient and hacky, but I could not figure out another way to accomplish this. Lastly, I am new to node.js, mongoose, and stackoverflow so forgive me if this is not the most appropriate place to extend this discussion. It's just that Josh's solution was the most helpful in my eventual "solution."
As this is an old question, I figured it might use an update.
To achieve asynchronous virtual fields, you can use mongoose-fill, as stated in mongoose's github issue: https://github.com/Automattic/mongoose/issues/1894