How to update in Vue3 component content based on reponse data? - asp.net

Recently I've been working on filters in my service for booking hotel rooms in .NET + Vue3.
Backend method for filtering works fine, but I don't have clue how to force component to update its content using fetched data.
Im reciving data in format like this:
enter image description here
Here are my script and component files:
Filters component:
<template>
<div class="container">
<div class="d-flex align-items-center">
<label for="first_day" class="p-2">First day: </label>
<input type="date" name="first_day" v-model="filtersOptions.FirstDay" />
<label for="last_day" class="p-2">Last day: </label>
<input type="date" name="last_day" v-model="filtersOptions.LastDay"/>
<button type="submit" class="m-2 p-2" v-on:click="fetchFilteredRooms()">Search</button>
</div>
</div>
</template>
<script lang="ts">
import { useFilters } from '#/composables/useFilters';
export default {
setup(props: any, context: any) {
const { filtersOptions, fetchFilteredRooms } = useFilters();
return {
filtersOptions,
fetchFilteredRooms,
}
}
}
</script>
Filters script:
import { ref } from 'vue';
import Consts from "#/consts";
import { useRooms } from './useRooms';
class FiltersOptions {
FirstDay: any;
LastDay: any;
};
const { Rooms } = useRooms();
export const useFilters = () => {
const filtersOptions = ref<any>(new FiltersOptions());
async function fetchFilteredRooms() {
const filterRoomsAPI = Consts.API.concat(`rooms/search`)
const headers = {
'Content-type': 'application/json; charset=UTF-8',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Origin': `${filterRoomsAPI}`
}
fetch(filterRoomsAPI, {
method: 'POST',
mode: 'cors',
credentials: 'same-origin',
body: JSON.stringify(filtersOptions._value),
headers
})
.then(response => response.json())
.then((data) => (Rooms.value = data))
.catch(error => console.error(error));
}
return {
Rooms,
filtersOptions,
fetchFilteredRooms,
}
}
Rooms component:
import { ref } from 'vue';
import Consts from "#/consts";
import { useRooms } from './useRooms';
class FiltersOptions {
FirstDay: any;
LastDay: any;
};
const { Rooms } = useRooms();
export const useFilters = () => {
const filtersOptions = ref<any>(new FiltersOptions());
async function fetchFilteredRooms() {
const filterRoomsAPI = Consts.API.concat(`rooms/search`)
const headers = {
'Content-type': 'application/json; charset=UTF-8',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Origin': `${filterRoomsAPI}`
}
fetch(filterRoomsAPI, {
method: 'POST',
mode: 'cors',
credentials: 'same-origin',
body: JSON.stringify(filtersOptions._value),
headers
})
.then(response => response.json())
.then((data) => (Rooms.value = data))
.catch(error => console.error(error));
}
return {
Rooms,
filtersOptions,
fetchFilteredRooms,
}
}
Rooms script:
import { ref } from 'vue';
import Consts from "#/consts"
const headers = {
'Content-type': 'application/json; charset=UTF-8',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Origin': `${Consts.RoomsAPI}`
}
export function useRooms() {
const Rooms = ref([]);
async function fetchRooms() {
fetch(Consts.RoomsAPI, { headers })
.then(response => response.json())
.then((data) => (Rooms.value = data))
.catch(error => console.log(error));
}
return {
Rooms,
fetchRooms,
};
}
Any idea how to deal with it?

Related

nextjs dynamic() import works in dev mode, but not the production, Nextjs13 pages dir #45582

I have an app with nextjs 13 pages dire.
i am using zoom web sdk for embed the meetings. it works just fine in the dev mode.
the proplem:
when i build the app everything working except that zoom component not showing up after build,
when visiting the /meeting page the component didn't exist.
const ZoomCall = dynamic(() => import("#components/Zoom/ZoomCall"), {
ssr: false,
loading: () => "Loading...",
});
the ZoomCall
import { useEffect } from "react";
import dynamic from "next/dynamic";
import { ZoomMtg } from "#zoomus/websdk";
import ZoomMtgEmbedded from "#zoomus/websdk/embedded";
import ZoomInputs from "./ZoomInputs";
import { useState } from "react";
import useTranslation from "next-translate/useTranslation";
export default function ZoomCall({
user,
meetingNumber,
setMeetingnumber,
passWord,
setMeetingPassword,
}) {
const { t } = useTranslation();
const [isComponentMounted, setIsComponentMounted] = useState(false);
const signatureEndpoint = "/api/zoom/signature";
const role = 0;
useEffect(() => {
ZoomMtg.setZoomJSLib("https://source.zoom.us/1.9.1/lib", "/av");
ZoomMtg.preLoadWasm();
ZoomMtg.prepareJssdk();
}, []);
function getSignature(e) {
e.preventDefault();
fetch(signatureEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
meetingNumber: meetingNumber,
role: role,
}),
})
.then((res) => res.json())
.then((response) => {
startMeeting(response.signature);
})
.catch((error) => {
console.error(error);
});
}
function startMeeting(signature) {
let meetingSDKElement = document.getElementById("meetingSDKElement");
const client = ZoomMtgEmbedded.createClient();
// document.getElementById("zmmtg-root").style.display = "block";
client.init({
debug: true,
zoomAppRoot: meetingSDKElement,
language: "en-US",
customize: {
meetingInfo: [
"topic",
"host",
"mn",
"pwd",
"telPwd",
"invite",
"participant",
"dc",
"enctype",
],
toolbar: {
buttons: [
{
text: "Custom Button",
className: "CustomButton",
onClick: () => {
console.log("custom button");
},
},
],
},
},
});
client.join({
sdkKey: process.env.NEXT_PUBLIC_ZOOM_API_KEY,
signature: signature,
meetingNumber: meetingNumber,
password: passWord,
userName: "..",
userEmail: "ah....",
});
}
return (
<div>
<div id="meetingSDKElement"></div>
<button onClick={getSignature}>Join Meeting</button>
</div>
);
}
I have moved the exports to a index file the export
import dynamic from "next/dynamic";
export default dynamic(() => import("./ZoomCall"), { ssr: false });
the issue in still
i have managed to solve this by changing the :
const ZoomCall = dynamic(() => import("#components/Zoom/ZoomCall"), {
ssr: false,
loading: () => "Loading...",
});
into
export default dynamic(() => import("./ZoomCall").then((mod) => mod.ZoomCall), {
ssr: false,
});
and it works just fine

GET http://localhost:3000/product/api/v1/product632f854a00da092efba3d125 404 (Not Found) unable to fetch ProductDetails

//PRODUCT_DETAILS
import React, { Fragment, useEffect } from "react";
import Carousel from "react-material-ui-carousel";
import { useSelector, useDispatch } from "react-redux";
import { getProductDetails } from "../../actions/productAction";
import { useParams } from "react-router-dom";
const ProductDetails = () => {
const { id } = useParams();
const dispatch = useDispatch();
const { product } = useSelector((state) => state.productDetails);
useEffect(() => {
dispatch(getProductDetails(id));
}, [dispatch, id]);
return (
<Fragment>
<div className="ProductDetails">
<div>
<Carousel>
{product &&
product.images &&
product.images.map((item, i) => (
<img
className="CarouselImage"
key={item.url}
src={item.url}
alt={`${i}Slide`}
/>
))}
</Carousel>
</div>
</div>
</Fragment>
);
};
export default ProductDetails;
//PRODUCT_REDUCER
import {
CLEAR_ERRORS,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from "../constants/productConstants";
export const productDetailsReducer = (state = { product: {} }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return {
loading: true,
...state,
};
case PRODUCT_DETAILS_SUCCESS:
return {
loading: false,
product: action.payload.product,
};
case PRODUCT_DETAILS_FAIL:
return {
loading: false,
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
//PRODUCT_ACTION
import axios from "axios";
import {
CLEAR_ERRORS,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
} from "../constants/productConstants";
export const getProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`api/v1/product${id}`);
dispatch({
type: PRODUCT_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
Payload: error.response.data.message,
});
}
};
// clearing errors
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
//PRODUCT_CONSTANTS

Refactoring reducers and actions in a feature based architecture style

This is a purely hypothetical example while I am trying to learn NextJs and I added Redux following the documentation from the official link
but I want to format the code in a certain way, similar to feature based architecture where each feature is encapsulated in a separate folder.
Please feel free to add, remove and suggest any changes, they are all most welcoming.
This is my general folder structure
components
public
pages
|-accounts
| |-index.tsx
|-_app.tsx
|-index.tsx
store
|-account
| |-accountsApi.ts
| |-actions.ts
| |-index.ts
| |-reducer.ts
| |-types.ts
|-index.ts
styles
package.json
...
And this is the Accounts component under accounts folder at pages.
I am dispatching the login action from action creators, is there any way to make the action accept an object with the credentials?
//pages/account/index.tsx
import Link from 'next/link'
import { useState } from 'react'
import Layout from '../../components/Layout'
import { useDispatch } from "react-redux"
import type { Account } from '../../store/account/types'
import { accoutActions } from '../../store/account'
type Props = {
accounts: Account[]
}
const Accounts: React.FunctionComponent<Props> = ({ accounts }) => {
const dispatch = useDispatch();
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = (event: any) => {
let obj = {
username: username,
password: password
}
dispatch(accoutActions.login(obj)); // how to set it up, so that the action accepts an object with credentials
}
return (
<Layout title="Accounts | Demo">
<h1>Accounts</h1>
<p>You are currently on: /accounts</p>
<p>
<Link href="/">
<a>Go home</a>
</Link>
</p>
<div>
<p>Login</p>
<div>
<label htmlFor="username">Username:</label>
<input onChange={e => setUsername(e.target.value)} type="text" id="username" name="username" value={username} />
<label htmlFor="password">Last name:</label>
<input onChange={e => setPassword(e.target.value)} type="text" id="password" name="password" value={password} />
<button type="submit" onClick={handleSubmit}>Login</button>
</div>
</div>
</Layout>
)
}
export default Accounts
Below is my account store with its respective files:
accountsAPI.ts where the api calls to the server are made, I will try to refactor later in a singleton pattern using axios
however I am learning what NextJs allows and what not.
import { CreateAccountInputModel, LoginInputModel } from "./types"
const url = 'https://127.0.0.1:8888/api/accounts'
export const createAccount = async (objModel: CreateAccountInputModel): Promise<any> => {
const response = await fetch(`${url}/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(objModel),
})
const result = await response.json()
return result
}
export const loginAccount = async(objModel: LoginInputModel): Promise<any> => {
const response = await fetch(`${url}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(objModel),
})
const result = await response.json()
return result
}
export const logoutAccount = async () : Promise<any> => {
const response = await fetch(`${url}/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
const result = await response.json()
return result
}
actions.ts contains account actions
import { createAction } from '#reduxjs/toolkit'
import { AccountActionTypes } from './types'
const create = createAction(AccountActionTypes.CREATE)
const login = createAction(AccountActionTypes.LOGIN)
const logout = createAction(AccountActionTypes.LOGOUT)
export const actionCreators = {
create,
login,
logout,
};
index.ts where I combine reducer from the reducer file and make a slice so that I can join all reducers to the store
I want to be able the reducers in a separate file
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { CreateAccountInputModel } from './types'
import type { AppState, AppThunk } from '..'
import { createAccount, loginAccount, logoutAccount } from './accountsAPI'
import { reducer, initialState } from './reducer'
export const createAsync = createAsyncThunk(
'account/create',
async (objModel: CreateAccountInputModel) => {
const response = await createAccount(objModel)
// The value we return becomes the `fulfilled` action payload
return response.data
}
)
export const accountSlice = createSlice({
name: 'account',
initialState,
reducers: reducer, // error: Type 'ReducerWithInitialState<AccountState>' is not assignable to type 'ValidateSliceCaseReducers<AccountState, SliceCaseReducers<AccountState>>'.
extraReducers: (builder) => {
builder
.addCase(createAsync.pending, (state) => {
state.status = 'loading'
})
.addCase(createAsync.fulfilled, (state, action) => {
state.status = 'idle'
})
},
})
export { actionCreators as accoutActions } from './actions';
export const isAuthenticated = (state: AppState) => state.account.isAuthenticated
export default accountSlice.reducer
Here are reducers reducer.ts and the initial state of the given reducer
import { createReducer, PayloadAction } from '#reduxjs/toolkit'
import { actionCreators } from './actions';
import { Account, AccountState } from './types';
export const initialState: AccountState = {
isAuthenticated: false,
token: '',
status: 'idle',
accounts: [] as Account[],
}
export const reducer = createReducer(initialState, (builder) => {
builder
.addCase(actionCreators.login, (state, action: PayloadAction<any>) => {
state.isAuthenticated = action.payload.isAuthenticated
state.token = action.payload.token
// can I do, return { ...state, ...} so that the previous state is preserved?
})
.addCase(actionCreators.logout, (state, action) => {
state.isAuthenticated = false
})
.addCase(actionCreators.create, (state, action: PayloadAction<any>) => {
state.accounts = [...state.accounts, action.payload]
}).addDefaultCase((state, action) => {
state.isAuthenticated = false
state.token = ''
})
})
All the types and models are stored in types.ts file.
I can write types.ts if it is required, but you can assume a general case of an account.
is there any way to make the action accept an object with the credentials?
const login = createAction<TypeOfObjectWithCredentials>(AccountActionTypes.LOGIN)
Calling login with an object will put it inside the payload automatically.

TypeError: Cannot perform 'get' on a proxy that has been revoked, redux-toolkit and nextJS

I'm get the error in tittle when a action is dispatched to redux in a next application and i can't find the solution: the first action is correctly dispatched but others raises the error: TypeError: Cannot perform 'get' on a proxy that has been revoked, redux-toolkit and nextJS, you can see the project in the follow link : https://github.com/cpereiramt/BACKBONE-TEST
Below I share the mainly snippets of code and the configuration in general :
configuring store:
import {
configureStore,
EnhancedStore,
getDefaultMiddleware
} from "#reduxjs/toolkit"
import { MakeStore } from "next-redux-wrapper"
import { Env } from "../constants"
import { rootReducer, RootState } from "./reducers"
import { createWrapper } from 'next-redux-wrapper';
/**
* #see https://redux-toolkit.js.org/usage/usage-with-typescript#correct-typings-for-the-dispatch-type
*/
const middlewares = [...getDefaultMiddleware<RootState>()]
const store = configureStore({
reducer: rootReducer,
middleware: middlewares,
devTools: Env.NODE_ENV === "development",
})
const makeStore: MakeStore = (_?: RootState): EnhancedStore => store
export const wrapper = createWrapper(makeStore);
combineReducers
import { combineReducers } from "redux"
import { contactReducer } from "./contact"
/**
* Combine reducers
* #see https://redux-toolkit.js.org/usage/usage-with-typescript
*/
export const rootReducer = combineReducers({
contacts: contactReducer,
})
export type RootState = ReturnType<typeof rootReducer>
actions
import { createAsyncThunk } from "#reduxjs/toolkit";
import { Contact } from "../../model";
import { FeatureKey } from "../featureKey";
/**
* Fetch all contact action
*/
export const fetchAllContactsAction = createAsyncThunk(
`${FeatureKey.CONTACT}/fetchAll`,
async (arg: { offset: number; limit: number }) => {
const { offset, limit } = arg
const url = `/api/contact?offset=${offset}&limit=${limit}`
const result: Contact[] = await fetch(url, {
method: "get",
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Fetch contact action
*/
export const fetchContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/fetch`,
async (arg: { id: number }) => {
const url = `/api/contact/${arg}`
const result: Contact = await fetch(url, {
method: "get",
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Add contact action
*/
export const addContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/add`,
async (arg: { contact: Contact }) => {
const url = `/api/contact`
const result: Contact = await fetch(url, {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(arg),
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Edit contact action
*/
export const editContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/edit`,
(arg: { contact: Contact }) => {
const { contact } = arg
const url = `/api/contact/${arg.id}`
const result: Contact = fetch(url, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(contact),
}).then((response: Response) => response.json())
return { contacts: result }
}
)
/**
* Delete contact action
*/
export const deleteContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/delete`,
async (arg: { id: number }) => {
const url = `/api/contact/${arg}`
await fetch(url, {
method: "delete",
})
}
)
reducers
import { ActionReducerMapBuilder, createReducer } from "#reduxjs/toolkit"
import {
addContactAction,
deleteContactAction,
editContactAction,
fetchAllContactsAction,
fetchContactAction
} from "./action"
import { adapter, ContactState, initialState } from "./state"
/**
* CONTACT reducer
*/
export const contactReducer = createReducer(
initialState,
(builder: ActionReducerMapBuilder<ContactState>) =>
builder
.addCase(fetchAllContactsAction.pending, (state) => {
return { ...state, isFetching: true }
})
.addCase(fetchAllContactsAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.setAll({ ...state, isFetching: false }, contacts)
})
.addCase(fetchAllContactsAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(fetchContactAction.pending, (state, action) => {
const { id } = action.meta.arg
return { ...state, isFetching: true, selectedId: id }
})
.addCase(fetchContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.upsertOne({ ...state, isFetching: false }, contacts)
})
.addCase(fetchContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(addContactAction.pending, (state, action) => {
const { contact } = action.meta.arg
return { ...state, isFetching: true, selectedId: contact?.id }
})
.addCase(addContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.addOne({ ...state, isFetching: false }, contacts)
})
.addCase(addContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(editContactAction.pending, (state, action) => {
const { contact } = action.meta.arg
return { ...state, isFetching: true, selectedId: contact?.id }
})
.addCase(editContactAction.fulfilled, (state, action) => {
const { contacts } = action.payload
return adapter.updateOne(
{ ...state, isFetching: false },
{
id: contacts.id,
changes: contacts,
}
)
})
.addCase(editContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
//-------------------------------------------------------------------------------
.addCase(deleteContactAction.pending, (state, action) => {
const { id } = action.meta.arg
return { ...state, isFetching: true, selectedId: id }
})
.addCase(deleteContactAction.fulfilled, (state, action) => {
const { id } = action.meta.arg
return adapter.removeOne({ ...state, isFetching: false }, id)
})
.addCase(deleteContactAction.rejected, (state) => {
return { ...state, isFetching: false }
})
)
selectors
import { createSelector } from "#reduxjs/toolkit"
import { RootState } from "../reducers"
import { adapter, ContactState } from "./state"
const { selectAll, selectEntities } = adapter.getSelectors()
const featureStateSelector = (state: RootState) => state.contacts
const entitiesSelector = createSelector(featureStateSelector, selectEntities)
/**
* isFetching selector
*/
export const isFetchingSelector = createSelector(
featureStateSelector,
(state: ContactState) => state?.isFetching
)
/**
* selectedId selector
*/
export const selectedIdSelector = createSelector(
featureStateSelector,
(state: ContactState) => state?.selectedId
)
/**
* all contact selector
*/
export const allContactSelector = createSelector(featureStateSelector, selectAll)
/**
* contact selector
*/
export const contactSelector = createSelector(
entitiesSelector,
selectedIdSelector,
(entities, id) => (id ? entities[id] || null : null)
)
states
import { createEntityAdapter, EntityState } from "#reduxjs/toolkit"
import { Contact } from "../../model"
export interface ContactState extends EntityState<Contact> {
isFetching: boolean
selectedId: number | null
}
export const adapter = createEntityAdapter<Contact>({
selectId: (contacts: Contact) => contacts.id,
})
export const initialState: ContactState = adapter.getInitialState({
isFetching: false,
selectedId: null,
})
And the _app file
import CssBaseline from "#material-ui/core/CssBaseline";
import { ThemeProvider } from "#material-ui/styles";
import { NextPageContext } from 'next';
import App from "next/app";
import React from "react";
import { MuiTheme } from "../components/MuiTheme";
import { Store } from '../redux/store';
import { wrapper } from "../store/configureStore";
import "../styles/main.css";
interface AppContext extends NextPageContext {
store: Store;
}
class MyApp extends App<AppContext> {
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side")
jssStyles?.parentNode?.removeChild(jssStyles)
}
render() {
const { Component, ...props } = this.props;
return (
<ThemeProvider theme={MuiTheme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...props} />
</ThemeProvider>
)
}
}
export default wrapper.withRedux(MyApp);
And in index file the action fetchAllContacts() work without problems.
import React, { useEffect } from "react";
import ContactTable from "../components/Table/";
import { useContact } from "../hooks";
import { Contact } from '../model/Contact';
import appStyles from "./indexStyles";
type Props = {}
function Index(props: Props) {
const { fetchAllContacts } = useContact();
const [contacts, setContacts] = React.useState<Contact[]>([])
useEffect(() => {
const results = fetchAllContacts();
results.then(data => console.log(data));
results.then(data => setContacts(data.contacts));
}, [])
const classes = appStyles(props)
return (
<div className={classes.indexBackground}>
<div className={classes.indexTabletDiv}>
<ContactTable contacts={contacts} />
</div>
</div>
);
}
export default Index
But when I try to use the action addContact() in another component the error is raised
page add
import { Button, createStyles, InputLabel, makeStyles, TextField, Theme } from "#material-ui/core";
import router from "next/router";
import React from "react";
import { useContact } from "../../hooks";
const useStyles = makeStyles((_: Theme) =>
createStyles({
root: {},
})
)
type Props = { }
const AddContact = (props: Props) => {
const { addContact } = useContact();
const newContact = {
id:'455666gghghttttytyty',
firstName: 'clayton',
lastName: 'pereira',
email: 'cpereiramt#gmail.com',
phone: '5565992188269',
}
const handleCreateContact = () => {
addContact(newContact);
}
const { } = props
return (
<div style={{margin: '10px', display: 'flex', justifyContent: 'space-between', wrap: 'wrap', flexDirection:'column'}}>
<>
<InputLabel>Name </InputLabel><TextField />
<InputLabel>Last Name </InputLabel><TextField />
<InputLabel>Email </InputLabel><TextField />
<div>
<Button variant="outlined" color="primary" onClick={() => handleCreateContact(newContact)} >
Create Contact</Button>
<Button variant="outlined" color="primary" onClick={() => router.push('/')} >
Back</Button>
</div>
</>
</div>
)
}
export default AddContact;
I think I see the issue.
First, the actual error message is Immer telling you that something is trying to modify the Proxy-wrapped state in a reducer, but long after the reducer has actually finished running. Normally that's impossible, because reducers are synchronous. So, there has to be some kind of async behavior going on.
The case reducers themselves seem basically okay, and mostly look like this:
.addCase(fetchAllContactsAction.pending, (state) => {
return { ...state, isFetching: true }
})
I'll point out that Immer lets you write state.isFetching = true instead, so you don't have to do object spreads :) But this code should run fine, and it's synchronous. So, what's the problem?
You didn't actually describe which actions are causing errors, so I'm having to guess. But, I think it's in one of the async thunks, and specifically, here:
export const editContactAction = createAsyncThunk(
`${FeatureKey.CONTACT}/edit`,
(arg: { contact: Contact }) => {
const { contact } = arg
const url = `/api/contact/${arg.id}`
// PROBLEM 1
const result: Contact = fetch(url, {
method: "put",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(contact),
}).then((response: Response) => response.json())
// PROBLEM 2
return { contacts: result }
}
Notice the line const result: Contact = fetch(). This doesn't have any await in there So, this is going to end up returning a Promise and saving it as result, and then that Promise ends up being returned as the contacts field.
I think that the object with the promise is being put into Redux state, wrapped by an Immer proxy, and then modified sometime later, and that's what's causing the error. But I'm not 100% sure because I don't know which actions are actually involved.

React-Redux Rails API: Page Not Updating On It's Own

I'm currently working on a react-redux project with a rails API in the back end and am having some issues. So I currently have an update method in the back end for my edit functionality in the front end. For the most part, it seems to work fine, it updates the back end when edited but it won't render the updated object in the front end until manually refresh the page.
I'm thinking this has something to do with my async function but I can't figure it out.
Thank you!
Here are my actions:
return (dispatch) => {
fetch(`http://localhost:3000/haikus`)
.then(res => res.json())
.then(haikus => {
dispatch({type: "FETCH_HAIKUS", payload: haikus})
})
}
}
export function addHaiku(haiku){
return (dispatch) => {
const options = {
method: "POST",
headers: {
"Content-type": "application/json",
"accept": "application/json"
},
body: JSON.stringify({haiku})
}
fetch(`http://localhost:3000/haikus`, options)
.then(res => res.json())
.then(haiku => {
dispatch({type: "ADD_HAIKU", payload: haiku})
})
}
}
export function editHaiku(haiku){
// debugger
return (dispatch) => {
const options = {
method: "PATCH",
headers: {
"Content-type": "application/json",
"accept": "application/json"
},
body: JSON.stringify({haiku})
}
fetch(`http://localhost:3000/haikus/${haiku.id}`, options)
.then(res => res.json())
.then(haiku => {
dispatch({type: "EDIT_HAIKU", payload: haiku})
// dispatch({type: "EDIT_HAIKU", payload: haiku.data})
})
}
}
export function deleteHaiku(haikuId){
return (dispatch) => {
const options = {
method: "DELETE"
}
fetch(`http://localhost:3000/haikus/${haikuId}`, options)
.then(res => res.json())
.then(message => {
dispatch({type: "DELETE_LIST", payload: haikuId})
})
}
}```
Here is my reducer:
```export default function haikuReducer(state, action){
// debugger
switch(action.type){
case "FETCH_HAIKUS":
return {
haikus: action.payload
}
case "ADD_HAIKU":
return {
haikus: [...state.haikus, action.payload]
}
case "DELETE_HAIKU":
const newHaiku = state.haikus.filter(haiku => haiku.id !== action.payload)
return {
haikus: newHaiku
}
case "EDIT_HAIKU":
// debugger
// const editHaiku = state.haikus.map(haiku => haiku.id === action.payload.id ? action.payload : haiku)
const editHaiku = state.haikus.map(haiku => haiku.id === parseInt(action.payload.id) ? action.payload : haiku)
return {
haikus: editHaiku
}
default:
return state
}
}```
Here is my form component:
```import React, {Component} from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import { addHaiku } from './actions/haikuActions'
import { editHaiku } from './actions/haikuActions'
class HaikuForm extends Component {
// Normally do not set state directly to props
constructor(props){
super(props)
this.state = {
id: this.props.haiku ? this.props.haiku.id : "",
title: this.props.haiku ? this.props.haiku.title : "",
haiku: this.props.haiku ? this.props.haiku.haiku : "",
genre: this.props.haiku ? this.props.haiku.genre : ""
}
}
handleSubmit(event) {
event.preventDefault()
if(!this.props.haiku){
this.props.addHaiku(this.state)
} else {
this.props.editHaiku(this.state)
}
this.setState({ title: "", haiku: "", genre: "" ,id: "" })
this.props.history.push('/haikus')
}
handleChange(event){
this.setState({
[event.target.name]: event.target.value
})
}
redirectOrRenderForm = () => {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text" onChange={(event) => this.handleChange(event)} value={this.state.title} name="title"/><br></br>
<input type="text" onChange={(event) => this.handleChange(event)} value={this.state.haiku} name="haiku"/><br></br>
<input type="text" onChange={(event) => this.handleChange(event)} value={this.state.genre} name="genre"/>
<input type="submit"/>
</form>
)
}
render(){
return (
<>
{this.redirectOrRenderForm()}
</>
)
}
}
export default withRouter(connect(null, { addHaiku, editHaiku })(HaikuForm))```

Resources