I would like two different slices to cross-reference each other's actions like so:
const sliceA = createSlice({
name: "sliceA",
initialState: null,
reducers: {
someReducer: (state, action) => {
// do something
},
},
extraReducers: {
[sliceB.actions.anotherReducer]: (state, action) => {
// do something
},
},
});
const sliceB = createSlice({
name: "sliceB",
initialState: null,
reducers: {
anotherReducer: (state, action) => {
// do something else
},
},
extraReducers: {
[sliceA.actions.someReducer]: (state, action) => {
// do something else
},
},
});
The problem is that I receive the error that sliceB is undefined when trying to set the extraReducers for sliceA.
I would like to keep the slices separate for clarity, but that some of their actions affect each other.
What is a good way to achieve that?
You have to resolve the circular dependency between the slices by factoring out the creation for the one of the actions into a createActions call that both createSlice calls can reference as extraReducers without their definition being circular.
also note your action naming, and then the line here, is misleading:
[sliceA.actions.someReducer]: (state, action) => {
You createSlice is making both actions and reducers, so use a name for the action that doesn't imply it's a reducer.
use createAction: https://redux-toolkit.js.org/api/createAction
See circular ref notes here: https://redux-toolkit.js.org/usage/usage-guide#exporting-and-using-slices
const actionOnAThatAffectsB = createAction('B_ACTION_NAME', function prepare(text) {
// this function is an optional parameter to createAction.
// only use it if you want to add to the eventual payload.
return {
payload: {
text,
id: nanoid(),
createdAt: new Date().toISOString()
}
}
})
const sliceB = createSlice({
name: "sliceB",
initialState: null,
reducers: {
actionOnBThatAffectsA: (state, action) => {
// do main stuff on A
},
},
extraReducers: {
[sliceA.actions.someAction]: (state, action) => {
// do other stuff.
},
},
});
const sliceA = createSlice({
name: "sliceA",
initialState: null,
reducers: {},
extraReducers: {
[sliceB.actions.actionOnBThatAffectsA]: (state, action) => {
// do extra stuff on A
},
[actionOnAThatAffectsB]: (state, action) => {
// you can't create the *action* here with createSlice's default creation through the reducers{} parameter — since that leads to a circular reference during creation.
// You *are* creating the *reducer* itself here, though.
},
},
});
Related
const Todo = mongoose.model("ToDo", {
text: String,
complete: Boolean
});
const yoga = createYoga({
schema: createSchema({
typeDefs: `
type Query {
todos: [Todo]
}
type Todo {
id: ID!
text: String!
complete: Boolean!
}
type Mutation {
createTodo(text: String!): Todo
updateTodo(id: ID!, complete: Boolean!): Boolean
removeTodo(id: ID!): Boolean
}
`,
resolvers: {
Query: {
todos: () => Todo.find()
},
Mutation: {
createTodo: async (_, { text }) => {
const todo = new Todo({ text, complete: false });
todo.save();
return todo;
},
updateTodo: async (_, { id, complete }) => {
Todo.findByIdAndUpdate(id, { complete });
return true;
},
removeTodo: async (_, { id }) => {
Todo.findByIdAndRemove(id);
return true;
}
}
},
}),
graphiql: {
title: 'To-Do Application',
defaultQuery: `{}`,
},
graphqlEndpoint: '/',
})
In GraphQL playground:
I can create a todo item.
I can query all items in my todo list
I cannot update or delete an item from the list. My query afterward returns my initial list.
"It looks like your post is mostly code; please add some more details. It looks like your post is mostly code; please add some more details"
Your problem is that the .save() function on a Mongoose model is async, so you have to await it
So add await in front of your todo.save(), Todo.findByIdAndUpdate() and todo.findByIdAndRemove()
i use redux toolkit with react native and mongodb (mongoose)
i delete item and it successfully deleted from db
but not in client and need to reload page
todoSlice :
import {createSlice} from '#reduxjs/toolkit';
export const todoSlice = createSlice({
name: 'todos',
initialState: {
todos: [],
pending: null,
error: null,
},
reducers: {
deleteTodo: (state, action) => {
return state
},
},
});
export const {deleteTodo} = todoSlice.actions;
export default todoSlice.reducer;
apiCall:
import axios from 'axios';
import {deleteTodo} from './todoSlice';
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
dispatch(deleteTodo());
} catch (err) {
console.log(err);
}
};
main :
const {todo} = useSelector(state => state);
const dispatch = useDispatch();
const {todos} = todo;
useEffect(() => {
getTodos(dispatch);
}, []);
const handleDelete = id => {
deleteOneTodo(id, dispatch);
};
you have to implement deleteTodo inside your todoSlice in order to remove the deleted id from your local state,
...
export const todoSlice = createSlice({
name: 'todos',
initialState: {
todos: [],
pending: null,
error: null,
},
reducers: {
deleteTodo: (state, action) => {
return state.filter((todo)=>todo.id!==action.payload.id);
},
},
});
...
and of course you have to pass the payload with the id of the todo you want to remove
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
dispatch(deleteTodo({id:id}));
} catch (err) {
console.log(err);
}
};
if you still have doubts you can follow this tutorial: https://www.youtube.com/watch?v=fiesH6WU63I
i just call 'getTodos' inside 'deleteOneTodo'
and delete 'deleteTodo' from reducer
i hope its a good practice
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
// i add this line =>
getTodos(dispatch);
} catch (err) {
console.log(err);
}
};
I'm learning ho to develop GraphQL service with express, express-graphql, **graphql, mongoose,
db.collection.find has an optional query parameter that specifies selection filter using query operators.
I wonder if it is possible to define a schema in which to define an argument for a query field that ultimately it is passed as it is to the collection find methods.
for example I expect that the graphql query:
{ todosQuerable(query: {title: "Andare a Novellara"})
{ _id, title, completed }
}
responds with:
{
"data": {
"todos": [
{
"title": "Andare a Novellara",
"completed": false
}
]
}
}
since in mongo
> db.Todo.find({title: 'Andare a Novellara'})
{ "_id" : ObjectId("600d95d2e506988bc4430bb7"), "title" : "Andare a Novellara", "completed" : false }
I'm thinking something like:
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: <???????????????> },
},
resolve: (source, { query }) => {
return new Promise((resolve, reject) => {
TODO.find(query, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
I have made a few attempts but have not been able to get an idea of which type I should use in this case
ho help reproduce the problem here the source repository of my tests
Please note that this works fine:
todosByTitle: {
type: new graphql.GraphQLList(TodoType),
args: {
title: { type: graphql.GraphQLString },
},
resolve: (source, { title }) => {
return new Promise((resolve, reject) => {
TODO.find({title: {$regex: '.*' + title + '.*', $options: 'i'}}, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
but what I'm looking for is something more generic: I would like to grab graphql field argument named query and pass it as is to the the query parameter of the mongo collection find.
So the good news is you can do whatever you want. The bad news is that:
You have to do it yourself
You have to add every searchable field, so you'll probably end up with two copies of the Todo object here.
The type you're looking for is just a custom input object type like this:
Notice the GraphQLInputObjectType below is different from GraphQLObjectType.
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
todosQuerable: {
...
type: new graphql.GraphQLList(TodoType),
...
args: {
query: { type: TodoQueryType },
},
...
}
These two queries work great!
(this is me using aliases so I can make the same query twice in one call)
{
titleSearch: todosQuerable(query:{ title:"Buy orange" }) {
_id
title
completed
}
idSearch: todosQuerable(query:{ _id:"601c3f374b6dcc601890048d" }) {
_id
title
completed
}
}
Footnote:
Just to have it said, this is generally a GraphQL anti-pattern, as this is building an API based on your database choices, rather than as a client-driven API.
Regex Edit as requested:
If you're trying to do regular expression lookups, you have to figure out how to programmatically convert your strings into regular expressions. i.e. your input is a string ("/Novellara/"), but mongoose requires passing a RegExp to do wildcards (/Novellara/, no quotes).
You can do that a number of ways, but I'll show one example. If you change your input fields to use two properties of value & isExpression, like below, you can do it, but you have to specifically craft your query, since it's no longer just a passthrough.
var ExpressableStringInput = new graphql.GraphQLInputObjectType({
name: 'ExpressableString',
fields: {
value: {
type: graphql.GraphQLString
},
isExpression:{
type: graphql.GraphQLBoolean,
defaultValue: false,
}
}
})
var TodoQueryType = new graphql.GraphQLInputObjectType({
name: 'TodoQuery',
fields: function () {
return {
_id: {
type: graphql.GraphQLID
},
title: {
type: ExpressableStringInput
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
// resolver
todosQuerable: {
type: new graphql.GraphQLList(TodoType),
args: {
query: { type: TodoQueryType },
},
resolve: async (source, { query }) => {
const dbQuery = {};
if (query.title.isExpression) {
dbQuery.title = new RegExp(query.title.value);
} else {
dbQuery.title = query.title.value;
}
return new Promise((resolve, reject) => {
TODO.find(dbQuery, (err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
your query would then look like
query {
todosQuerable(query:{ title: { value: "Buy.*", isExpression: true }}) {
_id
title
completed
}
}
This query makes sense in my mind. If I think about the form you would show to a user, there is probably an input box and a checkbox that says "is this a regular expression?" or something, which would populate this query.
Alternatively, you could do like string matching: if the first and last characters are "/", you automagically make it into a regex before passing it into mongoose.
use Angular4 and angular-datatables.
Everything looks fine (pagination, column sorting, export buttons), but when I try to print/copy/export a table, it only outputs the headers, even though the rest of the data is there.
Any ideas what's should I check?
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 20,
serverSide: true,
processing: true,
ajax: (dataTablesParameters: any, callback) => {
this.service.getServerSidePaganated(dataTablesParameters).subscribe(resp => {
that.cdrs = resp.data;
callback({
recordsTotal: resp.recordsTotal,
recordsFiltered: resp.recordsFiltered,
data: []
});
});
},
columns: [
{ data: 'source' },
{ data: 'destination' },
{ data: 'dateTime'},
{ data: 'callTime'},
{ data: 'timeOnHold'},
{ data: 'disposition'},
{ data: 'recordingFileName'}
],
// Declare the use of the extension in the dom parameter
dom: 'Bfrtip',
// Configure the buttons
buttons: [
'print',
'excel'
]
};
}
you should set the callback data
ajax: (dataTablesParameters: any, callback) => {
this.service.getServerSidePaganated(dataTablesParameters).subscribe(resp => {
// that.cdrs = resp.data; // remove this line
callback({
recordsTotal: resp.recordsTotal,
recordsFiltered: resp.recordsFiltered,
data: resp.data // set data
});
});
I have two collections Colors and Cars.
In the car possibly to choose the color.
How to save/update object in collection so that embed Color object in Car object?
Cars = new Mongo.Collection('cars');
Cars.attachSchema(new SimpleSchema({
colorId: {
label: 'Color',
type: String,
autoform: {
options: function () {
return Colors.find().map(function (p) {
return {label: p.colorName, value: p._id};
});
},
label: false
}
},
color: {
type: Object,
},
'color._id': {
type: String,
autoform: {
omit: true,
},
},
'color.colorName': {
type: String,
autoform: {
omit: true,
},
},
'color.colorCode': {
type: String,
autoform: {
omit: true,
},
},
}));
Colors = new Mongo.Collection('colors');
Colors.attachSchema(new SimpleSchema({
colorName: {
type: String,
label: "Color Name",
max: 20,
},
colorCode: {
type: String,
optional: true,
label: "Color Code",
autoform: {
afFieldInput: {
type: "color"
}
}
},
}));
I try use
AutoForm.hooks({ insertCarForm: {before: {
but it did not work
There are several ways that you can achieve this and the solution largly depends on any relevant packages that you might be using. It's hard to give a working example without seeing your existing code that creates new 'cards'. Nevertheless, here is an example using the core Meteor API.
Assuming you have some form Template defined (which I have called 'manageCar'), you would do something like this.
Define a Meteor Method to handle inserting/updating the Car.
Meteor.methods({
updateCar: function(carDoc) {
check(carDoc, { /* carDoc schema */ });
const color = Colors.findOne(carDoc.colorId);
carDoc.color = color;
if (carDoc._id) {
Cars.update(carDoc._id, {
$set: {
colorId: carDoc.colorId,
color: carDoc.color,
}
})
} else {
Cars.insert(carDoc);
}
},
});
Add an event handler for the form submission that calls the defined Method.
Template.manageCar.events({
'click .js-save-car'(event, instance) {
const data = {
_id: event.target._id.value,
colorId: event.target.colorId.value
};
Meteor.call('updateCar', data, function(error, result) {
if (!error) {
alert('Car updated successfully');
}
});
}
});
Long story short, you just need to make sure you have access to the Color id that you are saving for the Car and then make sure you perform a find on the Color collection to retrieve the necessary Color document, then use that for your Car insert or update.
Let me know if you have any questions or need further explanation.