Redux Toolkit - action from one slice to be caught in another slice - redux-toolkit

There's an action (addOrder) in an orderSlice
orderSlice.js
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
orders: []
};
const ordersSlice = createSlice({
name: 'orders',
initialState,
reducers: {
addOrder: {
reducer: (state, action) => {
state.orders.push(action.payload)
},
prepare: (orderItems, orderTotal) => {
const orderDate = new Date().toDateString();
return { payload: { orderDate, orderItems: orderItems, orderTotal: orderTotal }}
}
}
}
})
export const { addOrder } = ordersSlice.actions;
export default ordersSlice.reducer;
I'd like it to also affect the state in another slice (cartSlice). Once the 'addOrder' is fired, it should also bring the cartReducer to its initial state. Some googling suggested that I should use extrareducers for that but I'm really not getting its syntax. See below (not valid code in extrareducers)
cartSlice
import { createSlice } from '#reduxjs/toolkit';
import { addOrder } from './ordersSlice';
const initialState = {
items: {},
totalAmount: 0
};
const cartSlice = createSlice({
name: 'cart',
initialState: initialState,
reducers: {
addToCart: (state, action) => {
// p = product to be added or amended
const p = action.payload;
if (state.items[p.id]) {
// already exists
state.items[p.id].quantity += 1;
state.items[p.id].sum += p.price;
state.totalAmount += p.price;
} else {
state.items[p.id] = { price: p.price, quantity: 1, title: p.title, sum: p.price};
state.totalAmount += p.price;
}
},
removeFromCart: (state, action) => {
console.log('remove from cart');
console.log(action.payload);
const currentQuantity = state.items[action.payload].quantity;
console.log(currentQuantity);
if (currentQuantity > 1) {
state.items[action.payload].quantity -= 1;
state.items[action.payload].sum -= state.items[action.payload].price;
state.totalAmount -= state.items[action.payload].price;
} else {
state.totalAmount -= state.items[action.payload].price;
delete state.items[action.payload];
}
}
},
extraReducers: (builder) => {
builder
.addCase(addOrder(state) => {
return initialState;
})
}
});
export const { addToCart, removeFromCart } = cartSlice.actions;
export default cartSlice.reducer;

You're almost there! The builder.addCase function takes two arguments. The first is the action creator and the second is the case reducer. So you need a comma after addOrder.
extraReducers: (builder) => {
builder.addCase(addOrder, (state) => {
return initialState;
});
}

Related

React Query hook call from orval

I use orval to create interfaces, services and it also create React Query hooks. I have a hook that I cant figur out how to use. It wants some UseMutationOptions togheter with my data.
export const useGetUserContext = <TError = ErrorType<unknown>, TContext = unknown>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof getUserContext>>,
TError,
{ data: UserContextRequest },
TContext
>;
request?: SecondParameter<typeof customInstance>;
}) => {
const { mutation: mutationOptions, request: requestOptions } = options ?? {};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof getUserContext>>, { data: UserContextRequest }> = (
props,
) => {
const { data } = props ?? {};
return getUserContext(data, requestOptions);
};
return useMutation<Awaited<ReturnType<typeof getUserContext>>, TError, { data: UserContextRequest }, TContext>(
mutationFn,
mutationOptions,
);
};

How to insert a draft-js custom component/block

I'm trying to insert my custom block to the editorState of draft-js's editor. I can't seem to find any detailed information on how to accomplish this.
Block Renderer:
const blockRendererFn = (contentBlock) => {
const type = contentBlock.getType();
if (type === 'CustomTestChipBlock') {
return {
component: CustomTestChipBlock,
editable: false,
props: {
foo: 'bar',
},
};
}
}
Block Render Map:
import { DefaultDraftBlockRenderMap } from "draft-js";
import { Map } from 'immutable';
const blockRenderMap = Map({
CustomTestChipBlock: {
element: 'div',
}
}).merge(DefaultDraftBlockRenderMap);
My custom block (material ui chip):
import { Chip } from "#mui/material";
const CustomTestChipBlock = (props) => {
const { block, contentState } = props;
const { foo } = props.blockProps;
const data = contentState.getEntity(block.getEntityAt(0)).getData();
console.log("foo: "+foo)
console.log("data: "+data)
return (
<Chip label="test" size="small"/>
)
}
Now my problem is when I try to insert my custom block. I assume my method of insertion must be wrong. I tried multiple insertion methods but due to lack of any detailed information on the subject, all of them ended up not even running the console.log inside my custom component.
Insertion:
const addChip = () => {
setEditorState(insertBlock("CustomTestChipBlock"));
}
const insertBlock = (type) => {
// This is where I can't find any detailed info at all
const newBlock = new ContentBlock({
key: genKey(),
type: type,
text: "",
characterList: List(),
});
const contentState = editorState.getCurrentContent();
const newBlockMap = contentState.getBlockMap().set(newBlock.key, newBlock);
const newEditorState = ContentState.createFromBlockArray(
newBlockMap.toArray()
)
.set("selectionBefore", contentState.getSelectionBefore())
.set("selectionAfter", contentState.getSelectionAfter());
return EditorState.push(editorState, newEditorState, "add-chip");
};

fireEvent.click with parameters

I have a radio button that when I click it calls a function with two parameters in the function (value and step), and when I make a test with testing librairy I would like that when I simulate the click of the button I return the two parameters.
Button
<Form.Check
inline
label={'form.email'}
name="media"
value={`LETTER`}
type="radio"
data-testid="EMAIL"
onClick={(e) => {
const target = e.target as HTMLTextAreaElement;
changeMedia(target.value, data);
}}
/>
Function ChangeMedia
const changeMedia = (value: any, step: any) => {
step.definition?.steps
.filter((obj: any) => obj.media.includes(value))
.map((item: any) => {
if (value === 'EMAIL') {
item.media = 'LETTER';
return item;
} else if (value === 'LETTER') {
item.media = 'EMAIL';
return item;
}
return item;
});
return data;
};
Objet Step
const step = {
definition: {
steps: [
{
document_model: 'template_circularization',
email_model: 'email_circularization',
label: {
EN: 'Circularization',
FR: 'Circularisation',
},
media: 'LETTER',
name: 'mail_circularization',
onDemandOnly: true,
},
],
},
};
Test
const radio = screen.getByTestId('EMAIL');
fireEvent.click(radio, { target: { value: 'EMAIL, step: step } });
when I do a console.log of value and step I get this :
value EMAIL
step {}
what would be the solution to retrieve the step object via firevent.click ?

Does React-data-grid have a mechanism to handle pagination?

I didn't see anything in the docs for pagination. Is there a built-in mechanism for this, or would I have to implement it myself?
Here is an example of pagination (Infinite scrolling) in react data grid. I am using the scrollHeight,scrollTop and clientHeight properties to check whether to load next page.You need to modify your API's to support this type of pagination.
let columns = [
{
key: 'field1',
name: 'Field1 ',
},
{
key: 'field2',
name: 'Field2 ',
},
{
key: 'field3',
name: 'Field3',
},
]
export default class DataGrid extends React.Component {
constructor(props) {
super(props)
this.state = {height: window.innerHeight - 180 > 300 ? window.innerHeight - 180 : 300,page:1}
this.rowGetter = this.rowGetter.bind(this)
this.scrollListener = () => {
if (
(this.canvas.clientHeight +
this.canvas.scrollTop) >= this.canvas.scrollHeight) {
if (this.props.data.next !== null) {
let query = {}
let newpage = this.state.page +1
query['page'] = newpage
this.setState({'page':newpage})
this.props.dispatch(fetchData(query)).then(
(res) => {
// make handling
},
(err) => {
// make handleing
}
)
}
}
};
this.canvas = null;
}
componentDidMount() {
this.props.dispatch(fetchData({'page':this.state.page}))
this.canvas = findDOMNode(this).querySelector('.react-grid-Canvas');
this.canvas.addEventListener('scroll', this.scrollListener);
this._mounted = true
}
componentWillUnmount() {
if(this.canvas) {
this.canvas.removeEventListener('scroll', this.scrollListener);
}
}
getRows() {
return this.props.data.rows;
}
getSize() {
return this.getRows().length;
}
rowGetter(rowIndex) {
let rows = this.getRows();
let _row = rows[rowIndex]
return _row
}
render() {
return (
<ReactDataGrid columns={columns}
rowGetter={this.rowGetter}
rowsCount={this.getSize()}
headerRowHeight={40}
minHeight={this.state.height}
rowHeight={40}
/>
)
}
}
Note : Assumed data are taken from redux store

map array to another array in TypeScript

Can I improve my code and replace for-loop by array.map?
I searched and I think I could do that but I didn't find how could I apply that.
This is what I found :
var result = arr.map(person => ({ value: person.id, text: person.name }));
And my code is here:
public getFlights(): Observable<RowItem[]> {
return this.http
.get(this.apiHostFlights)
.map((res: any) => {
return <LocationModelItem[]>res.json();
})
.map((items: LocationModelItem[]) => {
var rowItems: RowItem[]=[];
var cachedLenght = items.length;
for (var i = 0; i < cachedLenght; i++) {
rowItems.push(
new RowItem(i, items[i].name, items[i].img, items[i].category)
);
}
return rowItems;
})
.catch((error: any) => {
return Observable.throw(error.statusText);
});
}
public getFlights(): Observable<RowItem[]> {
return this.http
.get(this.apiHostFlights)
.map((res: any) => {
return <LocationModelItem[]>res.json();
})
.map((items: LocationModelItem[]) => {
return items.map((item, index) => ({
index: index,
name: item.name,
img: item.img,
category: item.category)
}))
})
.catch((error: any) => {
return Observable.throw(error.statusText);
});
}
This should do it hopefully