how to update the state using diff way - reactjs

I am define the state in react "react": "^17.0.0", like this:
export interface IRoleState {
data: API.InterviewList,
menus: API.MenuItem,
meta: {
total: number
per_page: number
page: number
}
}
when I received the response from server, I want to update the menus item like this:
*getMenuTree({payload: params}, effects) {
if(!params) return;
const data = yield effects.call(menuPage, params)
if (data) {
yield effects.put({
type: 'getTree',
payload: {
menus: data
}
})
}
},
But now I found it override all values with menus, how to update the menu item using the diff way? Only update menus, keep other value do not change. I have tried like this:
yield effects.put({
type: 'getTree',
payload: {
...,
menus: data
}
})
seems could not work. This is the full code of the state:
import { Effect, Reducer, Subscription } from 'umi';
import { rolePage } from '#/services/ant-design-pro/permission/role/role';
import { menuPage } from '#/services/ant-design-pro/permission/menu/menu';
export interface IRoleState {
data: API.InterviewList,
menus: API.MenuItem,
meta: {
total: number
per_page: number
page: number
}
}
interface IRoleModel {
namespace: 'roles'
state: IRoleState
reducers: {
getPage: Reducer<IRoleState>,
getTree: Reducer<IRoleState>
}
effects: {
getRolePage: Effect,
getMenuTree: Effect
}
subscriptions: {
setup: Subscription
}
}
const RoleModel: IRoleModel = {
namespace: 'roles',
state: {
data: {},
menus: {},
meta: {
current: 1,
pageSize: 10,
page: 1
}
},
reducers: {
getPage(state, action) {
return action.payload
},
getTree(state, action){
return action.payload
},
},
effects: {
*getRolePage({payload: params}, effects) {
if(!params) return;
const data = yield effects.call(rolePage, params)
if (data) {
yield effects.put({
type: 'getPage',
payload: {
data: data,
meta: {
...params
}
}
})
}
},
*getMenuTree({payload: params}, effects) {
if(!params) return;
const data = yield effects.call(menuPage, params)
if (data) {
yield effects.put({
type: 'getTree',
payload: {
menus: data
}
})
}
},
},
subscriptions: {
setup({ dispatch, history }, done) {
return history.listen((location, action) => {
if(location.pathname === '/users' || location.pathname === '/my') {
dispatch({
type: 'getRemove',
payload: {
page: 1,
per_page: 5
}
})
}
})
}
}
};
export default RoleModel;

I assume you are using redux-saga, in that case, you can spread the current state in your redux reducer.
...
return {
...state,
menus: action.payload.menus
}
...
Or You can get the data in saga via selectors and send the whole data to reducer but I won't recommend that.

Related

Update Values of Multiple Array in Redux

I'm updating an array and I wanted to update the productCode based on the given newCode response. This is by clicking the 'CREATE ALL PRODUCTS' button.
I'm thinking that the problem is on the reducer. It's currently not updating the productCode and newProductCode
Tip: productIndex is the key to finding it
Click Here: CODESANDBOX
Action
export const createAllProducts = (products) => async (dispatch) => {
try {
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_REQUEST
});
const responses = [
{
config: null,
data: {
newCode: "NEW_AA"
},
headers: null
},
{
config: null,
data: {
newCode: "NEW_FF"
},
headers: null
},
{
config: null,
data: {
newCode: "NEW_GG"
},
headers: null
}
];
const finalResponses = responses.map((product, index) => ({
newProductCode: product.data.newCode,
productCode: product.data.newCode,
productIndex: products[index].productIndex
}));
console.log(finalResponses);
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_SUCCESS,
payload: finalResponses
});
} catch (error) {
dispatch({
type: appConstants.CREATE_ALL_PRODUCTS_FAILURE
});
}
};
Reducer
case appConstants.CREATE_ALL_PRODUCTS_SUCCESS:
const updatedProducts = state.products.map((product, index) => {
const found = action.payload.find((el) => el.productIndex === index);
return found
? {
...updatedProducts,
productCode: found.productCode,
newProductCode: found.newProductCode
}
: product;
});
return {
...state,
isCreatingAllProducts: false,
products: updatedProducts
};
The issue is with the reducer
case appConstants.CREATE_ALL_PRODUCTS_SUCCESS:
return {
...state,
products: state.products.map((product, index) => {
const found = action.payload.find((el) => el.productIndex === index);
console.log(found);
return found
? {
...product,
productCode: found.productCode,
newProductCode: found.newProductCode
}
: product;
})
};
You used reduce methods with the initial value state, which is the actually old state.
Consider this example:
const state = { history: null }
const payload = [ 'hello', 'equal' ]
//your current reducer
const newState = payload.reduce((acc, cur) => { acc[cur] = cur; return acc } , state)
//the state reference point to the same obj, then redux will not trigger re-render
console.log(newState === state) // true

React Apollo Delay updating Cache After Mutation

I tried so hard to update Apollo cache after running Mutation, but i couldn't be able to remove 1 second delay after the mutation.
I followed 'ac3-state-management-examples' for solve this problem, but still couldn't find any problem.
This is my client-side code.
export const DELETE_ITEM_IN_CART = gql`
mutation DeleteItemInCart($cartItemId: String!) {
DeleteItemInCart(cartItemId: $cartItemId)
}
`;
export function useDeleteItemInCart() {
console.log(`DELETION START! ${Date()}`);
const [mutate, { data, error }] = useMutation<
DeleteItemInCartType.DeleteItemInCart,
DeleteItemInCartType.DeleteItemInCartVariables
>(DELETE_ITEM_IN_CART, {
update(cache, { data }) {
const deletedCartItemId = data?.DeleteItemInCart;
const existingCartItems = cache.readQuery<myCart>({
query: MY_CART,
});
if (existingCartItems && deletedCartItem && existingCartItems.myCart) {
cache.writeQuery({
query: MY_CART,
data: {
myCart: {
cartItem: existingCartItems.myCart.cartItem.filter(
t => t.id !== deletedCartItemId,
),
},
},
});
console.log(`DELETION OVER! ${Date()}`);
}
},
});
return { mutate, data, error };
}
And here's my server-side mutation
export const DeleteItemInCart = mutationField('DeleteItemInCart', {
args: {cartItemId: nonNull('String')},
type: nonNull('String'),
description: 'Delete an item in my cart',
resolve: (_, {cartItemId}, ctx) => {
const {prisma} = ctx;
try {
prisma.cartItem.delete({
where: {
id: cartItemId,
},
});
return cartItemId;
} catch (error) {
return cartItemId;
}
},
});
This is an example of Apollo-remote-state-mananagement
export const DELETE_TODO = gql`
mutation DeleteTodo ($id: Int!) {
deleteTodo (id: $id) {
success
todo {
id
text
completed
}
error {
... on TodoNotFoundError {
message
}
}
}
}
`
export function useDeleteTodo () {
const [mutate, { data, error }] = useMutation<
DeleteTodoTypes.DeleteTodo,
DeleteTodoTypes.DeleteTodoVariables
>(
DELETE_TODO,
{
update (cache, { data }) {
const deletedTodoId = data?.deleteTodo.todo?.id;
const allTodos = cache.readQuery<GetAllTodos>({
query: GET_ALL_TODOS
});
cache.writeQuery({
query: GET_ALL_TODOS,
data: {
todos: {
edges: allTodos?.todos.edges.filter((t) => t?.node.id !== deletedTodoId)
},
},
});
}
}
)
return { mutate, data, error };
}
Any advice?
1 second delay is inevitable using apollo cache?
I took a short video of my issue. i dont think it's inevitable...

How to apply useIntl language translation in a TypeScript file? Or is there any alternative/preferred workaround?

I am current having a quick startup on an Ant Design Pro project, when I try to implement translation with useIntl function from umi, it always give me a Invalid hook call error. I tried several workarounds to fix it but failed.
Here are my codes:
src/pages/user/login/model.ts
import { Effect, history, Reducer, useIntl } from 'umi';
import { message } from 'antd';
import { parse } from 'qs';
import { fakeAccountLogin, getFakeCaptcha } from './service';
import { extend } from 'lodash';
export function getPageQuery() {
return parse(window.location.href.split('?')[1]);
}
export function setAuthority(authority: string | string[]) {
const proAuthority = typeof authority === 'string' ? [authority] : authority;
localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
// hard code
// reload Authorized component
try {
if ((window as any).reloadAuthorized) {
(window as any).reloadAuthorized();
}
} catch (error) {
// do not need do anything
}
return authority;
}
export interface StateType {
status?: 'ok' | 'error';
type?: string;
currentAuthority?: 'user' | 'guest' | 'admin';
}
export interface ModelType {
namespace: string;
state: StateType;
effects: {
login: Effect;
getCaptcha: Effect;
};
reducers: {
changeLoginStatus: Reducer<StateType>;
};
}
const Model: ModelType = {
namespace: 'userAndlogin',
state: {
status: undefined,
},
effects: {
*login({ payload }, { call, put }) {
const response = yield call(fakeAccountLogin, payload);
yield put({
type: 'changeLoginStatus',
payload: response,
});
// Login successfully
if (response.status === 'ok') {
const intl = useIntl();
// Error Here //
message.success(intl.formatMessage({ id: 'userandlogin.login.success' }));
const urlParams = new URL(window.location.href);
const params = getPageQuery();
let { redirect } = params as { redirect: string };
if (redirect) {
const redirectUrlParams = new URL(redirect);
if (redirectUrlParams.origin === urlParams.origin) {
redirect = redirect.substr(urlParams.origin.length);
if (redirect.match(/^\/.*#/)) {
redirect = redirect.substr(redirect.indexOf('#') + 1);
}
} else {
window.location.href = redirect;
return;
}
}
history.replace(redirect || '/');
}
},
*getCaptcha({ payload }, { call }) {
yield call(getFakeCaptcha, payload);
},
},
reducers: {
changeLoginStatus(state, { payload }) {
setAuthority(payload.currentAuthority);
return {
...state,
status: payload.status,
type: payload.type,
};
},
},
};
export default Model;
The error is from the line
message.success(intl.formatMessage({ id: 'userandlogin.login.success' }));
Initially I thought it might cause by I used the React function in the Typescript file, so I tried to call the message.success in another global service through event, but the same thing happened, so I guess, is that any mistake I made in declaring the const intl in a model response part (maybe not the actual phrase for it, if not understand I can explain further)?
Edited 1:
As references, here is the source of the original project.
Ant Design Pro
Found Solution
getIntl(getLocale()).formatMessage({id:''});
From: Github

Passing data through a GRAPHQL Subscription gives null on only one of the arguments

I have the following GRAPHQL subscription:
Schema.graphql
type Subscription {
booking: SubscriptionData
}
type SubscriptionData {
booking: Booking!
action: String
}
And this is the resolver subsrciption file
Resolver/Subscription.js
const Subscription = {
booking: {
subscribe(parent, args, { pubsub }, info) {
return pubsub.asyncIterator("booking");
}
}
};
export default Subscription;
Then I have the following code on the Mutation in question
pubsub.publish("booking", { booking: { booking }, action: "test" });
I have the follow subscription file in front end (React)
const getAllBookings = gql`
query {
bookings {
time
durationMin
payed
selected
activity {
name
}
}
}
`;
const getAllBookingsInitial = {
query: gql`
query {
bookings {
time
durationMin
payed
selected
activity {
name
}
}
}
`
};
class AllBookings extends Component {
state = { allBookings: [] }
componentWillMount() {
console.log('componentWillMount inside AllBookings.js')
client.query(getAllBookingsInitial).then(res => this.setState({ allBookings: res.data.bookings })).catch(err => console.log("an error occurred: ", err));
}
componentDidMount() {
console.log(this.props.getAllBookingsQuery)
this.createBookingsSubscription = this.props.getAllBookingsQuery.subscribeToMore(
{
document: gql`
subscription {
booking {
booking {
time
durationMin
payed
selected
activity {
name
}
}
action
}
}
`,
updateQuery: async (prevState, { subscriptionData }) => {
console.log('subscriptionData', subscriptionData)
const newBooking = subscriptionData.data.booking.booking;
const newState = [...this.state.allBookings, newBooking]
this.setState((prevState) => ({ allBookings: [...prevState.allBookings, newBooking] }))
this.props.setAllBookings(newState);
}
},
err => console.error(err)
);
}
render() {
return null;
}
}
export default graphql(getAllBookings, { name: "getAllBookingsQuery" })(
AllBookings
);
And I get the following response:
data: {
booking: {booking: {...} action: null}}
I get that I am probably setting up the subscription wrong somehow but I don't see the issue.
Based on your schema, the desired data returned should look like this:
{
"booking": {
"booking": {
...
},
"action": "test"
}
}
The first booking is the field on Subscription, while the second booking is the field on SubscriptionData. The object you pass to publish should have this same shape (i.e. it should always include the root-level subscription field).
pubsub.publish('booking', {
booking: {
booking,
action: 'test',
},
})

React Redux form - When I trigger an update action and call a dispatch on another function, my list doesn't get updated

I have a submit function in a redux form that is supposed to update a list of customers once a user either hits "Create customer" or "Update customer".
When the user clicks "Create customer", it creates the customer and fires a this.props.dispatch(fetchCustomers()); to grab the updated list. However, when I click "Update Customer", it doesn't fetch the updated data. What am I doing wrong here?
Here is my function in my component:
submit(formProps) {
const customer = {
name: formProps.get('name'),
director: formProps.get('director'),
manager: formProps.get('manager'),
supervisor: formProps.get('supervisor'),
contact: {
name: formProps.get('contact').get('name'),
phone_number: formProps.get('contact').get('phone_number'),
email: formProps.get('contact').get('email'),
},
};
// Are we editing?
if(this.state.isEditingCustomer) {
customer['_id'] = this.state.customer._id;
this.props.dispatch(updateCustomer(customer));
} else {
// Dispatch action to convert the request to a work order
this.props.dispatch(createCustomer(customer));
}
// Get the users
this.props.dispatch(fetchCustomers());
// Close the modal
this.handleClose();
}
Here is the action creator:
export function updateCustomer(customer) {
return {
type: UPDATE_CUSTOMER,
payload: customer,
url: `/customer/${customer._id}`,
success: UPDATE_CUSTOMER_SUCCESS,
};
}
I am using sagas, so here is my updateCustomerFlow Saga:
export function* updateCustomerFlow() {
while (true) {
const request = yield take(UPDATE_CUSTOMER);
const promise = yield call(putData1, true, request.url, request.payload);
if (promise.status === 200) {
yield put({ type: request.success, payload: promise.data.payload });
}
}
}
Here is my fetchCustomers action creator:
export function fetchCustomers() {
return {
type: FETCH_CUSTOMERS,
payload: null,
url: '/customers/',
success: FETCH_CUSTOMERS_SUCCESS,
};
}
And my fetchCustomers saga:
export function* fetchCustomersFlow() {
while (true) { // eslint-disable-line no-constant-condition
const request = yield take(FETCH_CUSTOMERS);
const promise = yield call(getData1, true, request.url, request.payload);
if (promise.status === 200) {
yield put({ type: request.success, payload: promise.data.customers });
}
}
}

Resources