I am using Redux Toolkit in my application, where I want to update state with dynamic keys(state[keys]). I am getting error as Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'RootFormState'
For example:
//someSlice.ts
interface FormState<T> {
isValid: boolean;
answer: T;
}
interface RootFormState {
breakfast: FormState<Array>;
lunch: FormState<string>;
dinner: FormState<Array>;
}
const initialState:RootFormState = {
breakfast: {
isValid: false,
answer: []
},
lunch: {
isValid: false,
answer: ""
},
dinner: {
isValid: false,
answer: []
}
}
const foodSlice = createSlice({
name: 'food',
initialState,
reduce: {
updateAnswer: (state, action) => {
const {id, answer} = action.payload;
state[id].answer = answer;
}
}
})
RTK exports a PayloadAction type you can use it to type the action parameter of the reducer.
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
interface FormState<T> {
isValid: boolean;
answer: T;
}
interface RootFormState {
breakfast: FormState<Array<string>>;
lunch: FormState<string>;
dinner: FormState<Array<string>>;
}
const initialState: RootFormState = {
breakfast: { isValid: false, answer: [] },
lunch: { isValid: false, answer: '' },
dinner: { isValid: false, answer: [] },
};
type ValueOf<T> = T[keyof T];
const foodSlice = createSlice({
name: 'food',
initialState,
reducers: {
updateAnswer: (
state,
action: PayloadAction<{ id: keyof RootFormState; answer: ValueOf<RootFormState>['answer'] }>,
) => {
const { id, answer } = action.payload;
state[id].answer = answer;
},
},
});
Related
I am new to using RTK and typescript and I have a problem with (Property 'pending' does not exist on type '() => Promise >') in my extraReducers. I don't see any similar situation online as mine and suspect I am missing something very simple. However, I can't seem to find it.
It's my axios api call function:
export const getUser = () =>
apiCallWithAuthToken.get<UserResponse>("users/me");
My AsyncThunk:
export const getUserData = createAsyncThunk(
"user/getUser", async () => {
const response = await getUser();
return response.data;
}
);
Types and initial state:
interface userState {
id: string;
walletAddress: string;
email: string;
firstName: string;
lastName: string;
reflink: string;
createdAt: string;
modifiedAt: string;
isLogged: boolean;
loading: boolean;
}
const initialState: userState = {
id: "",
walletAddress: "",
email: "",
firstName: "",
lastName: "",
reflink: "",
createdAt: "",
modifiedAt: "",
isLogged: false,
loading: false
};
it's my store:
import { configureStore } from "#reduxjs/toolkit";
import userSlice, { getUserData } from "./features/user/userSlice";
export const store = configureStore({
reducer: {
user: userSlice,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
extraArgument: getUserData,
},
serializableCheck: false,
}),
})
and it's my slice:
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getUser.pending, (state) => {
state.loading = true;
})
}
});
export default userSlice.reducer;
I got a typescript error, why? Error image below:error image
I'm experiencing some difficulty with trying to map through an objects property which is an array of objects. I get back an error message that .map is undefined, basically saying there is no array. The array i want to map through is education. At times I also get a proxy error that the route is timing out and this loses the profile data i'm fetching and the profile object is empty. Is there anyway to fix this too.
My model:
import mongoose from 'mongoose'
const profileSchema = new mongoose.Schema(
{
experience: [
{
title: {
type: String,
required: true,
},
company: {
type: String,
required: true,
},
location: {
type: String,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
education: [
{
level: {
type: String,
required: true,
enum: [
'None',
'GCSE or equivalent',
'A-Level or equivalent',
'Certificate of Higher Education',
'Diploma of Higher Education',
'Bachelors',
'Masters',
'PhD',
],
},
school: {
type: String,
required: true,
},
fieldofstudy: {
type: String,
required: true,
},
city: {
type: String,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
},
],
skills: [
{
name: {
type: String,
required: true,
},
yearsExperience: {
type: Number,
required: true,
},
},
],
additionalInfo: {
desiredJobTitle: {
type: String,
},
desiredJobType: {
type: [String],
},
desiredSalary: {
type: Number,
},
readyToWork: {
type: Boolean,
default: false,
},
relocate: {
type: Boolean,
default: false,
},
},
savedJobs: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Job',
},
],
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
},
{ timestamps: true }
)
const Profile = mongoose.model('Profile', profileSchema)
export default Profile
My action:
import axios from 'axios'
import {
PROFILE_DETAILS_REQUEST,
PROFILE_DETAILS_SUCCESS,
PROFILE_DETAILS_FAIL,
PROFILE_CREATE_REQUEST,
PROFILE_CREATE_SUCCESS,
PROFILE_CREATE_FAIL,
PROFILE_CREATE_EDUCATION_REQUEST,
PROFILE_CREATE_EDUCATION_SUCCESS,
PROFILE_CREATE_EDUCATION_FAIL,
} from '../constants/profileConstants'
import { setAlert } from './alertActions'
export const getCurrentProfile = () => async (dispatch, getState) => {
try {
dispatch({ type: PROFILE_DETAILS_REQUEST })
const {
userLogin: { userData },
} = getState()
const config = {
headers: {
Authorization: `Bearer ${userData.token}`,
},
}
const { data } = await axios.get('/api/v1/profile/me', config)
dispatch({
type: PROFILE_DETAILS_SUCCESS,
payload: data,
})
} catch (error) {
dispatch({
type: PROFILE_DETAILS_FAIL,
payload:
error.response && error.response.data.error
? error.response.data.error
: null,
})
}
}
My reducer:
export const profileDetailsReducer = (state = { profile: {} }, action) => {
switch (action.type) {
case PROFILE_DETAILS_REQUEST:
return {
...state,
loading: true,
}
case PROFILE_DETAILS_SUCCESS:
return {
loading: false,
profile: action.payload,
}
case PROFILE_DETAILS_FAIL:
return {
loading: false,
error: action.payload,
}
case PROFILE_DETAILS_RESET:
return {
profile: {},
}
default:
return state
}
}
My dashboard component:
import React, { useEffect } from 'react'
import Moment from 'react-moment'
import { useDispatch, useSelector } from 'react-redux'
import { getCurrentProfile } from '../actions/profileActions'
import Loader from '../components/layout/Loader'
import DashboardActions from '../components/dashboard/DashboardActions'
const Dashboard = ({ history }) => {
const dispatch = useDispatch()
const profileDetails = useSelector((state) => state.profileDetails)
const { loading, error, profile } = profileDetails
const userLogin = useSelector((state) => state.userLogin)
const { userData } = userLogin
console.log(profile)
useEffect(() => {
if (!userData) {
history.push('/login')
} else {
dispatch(getCurrentProfile())
}
}, [dispatch, history, userData])
return (
<>
<h1 class='mb-4'>Dashboard</h1>
<p>Welcome {userData && userData.name}</p>
<br />
{loading ? (
<Loader />
) : (
<>
<DashboardActions />
<h2 className='my-2'>Education Details</h2>
<table className='table'>
<thead>
<tr>
<th>Level</th>
<th>Field of study</th>
<th>School</th>
</tr>
</thead>
<tbody>{profile.education.map((edu) => console.log(edu))}</tbody>
</table>
</>
)}
</>
)
}
export default Dashboard
Issue
state.profile.educatioin is undefined in your initial state.
export const profileDetailsReducer = (state = { profile: {} }, action) => { ...
Solution(s)
Define an initial state that contains an education array
const initialState = {
profile: {
education: [],
},
};
export const profileDetailsReducer = (state = initialState, action) => { ...
Or provide a fallback value from your selector
const { loading, error, profile: { education = [] } } = profileDetails;
...
<tbody>{education.map((edu) => console.log(edu))}</tbody>
Or provide the fallback in the render
<tbody>{(profile.education ?? []).map((edu) => console.log(edu))}</tbody>
This question already has answers here:
All reducers will be invoked when an action is dispatched?
(3 answers)
Closed 2 years ago.
I am learning about Redux and I have two reducers, a contactReducer to show contacts on the page and a testReducer to just mess around with. In one of my component files I have this function:
const mapDispatchToProps = (dispatch) => ({
getContacts: () => dispatch({ type: "TEST_ACTION" }),
});
These are my two reducer files:
contactReducer:
import { GET_CONTACTS } from "../actions/types";
const initialState = {
contacts: [
{
id: 1,
name: "John Doe",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "Karen Williams",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "Henry Johnson",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_CONTACTS:
return {
...state,
};
default:
console.log("testing action in contactReducer");
return state;
}
}
and testReducer:
import { GET_CONTACTS } from "../actions/types";
const initialState = {
contactsTest: [
{
id: 1,
name: "ffffffffffff",
email: "john#gmail.com",
phone: "555-555-5555",
},
{
id: 2,
name: "ggggggggggggg",
email: "karen#gmail.com",
phone: "444-444-4444",
},
{
id: 3,
name: "aaaaaaaaaaaaaa",
email: "henry#gmail.com",
phone: "333-333-333",
},
],
};
export default function (state = initialState, action) {
switch (action.type) {
case "TEST_ACTION":
return {
...state,
};
default:
console.log("testing action");
return state;
}
}
So, what I noticed from the console.log statements in the reducer files was that for every contact, both the contactReducer and testReducer's function was called with this line:
getContacts: () => dispatch({ type: "TEST_ACTION" }),
});
What if I have multiple reducers but I only want to call one of their functions for dispatch, what would I do?
combineReducers, Is a helper function in redux that helps you divide your reducers. take a look at this link: LINK
I'm trying to set state of input fields.
I want to update specific input value when a key is pressed.
setState state looks fine, but I'm getting error on map function of render part.
(this.state.form[this.state.mode].form.map)
I don't understand why it breaks on map.
export type IMode = "login" | "forgot";
export interface IInput {
value: string;
error: string;
id: string;
name: string;
placeholder: string;
type: "text" | "password";
}
export interface IState {
isRequestPending: boolean;
backendError: string;
mode: IMode;
form: {
[key in IMode]: {
name: string;
method: string;
action: string;
fields: IInput[];
};
};
}
in constructor
this.state = {
isRequestPending: false,
backendError: "",
mode: "login",
form: {
login: {
name: "Login",
method: "",
action: "",
fields: [
{
value: "",
error: "",
id: "test",
placeholder: "Login",
type: "text",
name: "login"
}
]
},
.... and so on
}
};
private handleFormInput = (e, input: IInput, index) => {
this.setState((prevState) => ({
...prevState,
backendError: "",
form: {
...prevState.form,
[this.state.mode]: {
...prevState.form[this.state.mode],
fields: {
...prevState.form[this.state.mode].fields,
[index]: {
...prevState.form[this.state.mode].fields[index],
value: "it Works, but map crash"
}
}
}
}
}
You are converting fields into an object which doesn't contain a map method. Update your state like this
this.setState(prev =>({
/*...*/
fields : prev.form[this.state.mode].fields.map((field, i) =>{
if(i !== index) return field
return {
...field,
value : 'foo'
}
})
}))
When updating a single value in shared, how do I spread in the other existing values
interface StateInterface {
env: {
apiUrl: string;
};
display: {
modal: string;
};
shared: {
avatar: string;
email: string;
fullName: string;
loggedIn: boolean;
language: string;
warningMessage: string;
};
}
const InitialState: StateInterface = {
env: {
apiUrl: 'http://localhost:8888',
},
display: {
modal: '',
},
shared: {
avatar: '',
email: '',
fullName: 'John Doe',
language: 'en',
loggedIn: false,
warningMessage: '',
},
};
const Reducer = (state: StateInterface, action: any) => {
switch (action.type) {
case 'SET_AVATAR':
return {
...state,
shared: {avatar: action.value}, // <<-- Will the other items in shared get overwriten, if so how to spread them in?
};
...
return {
...state,
shared: {...state.shared, avatar: action.value}, // <<-- Will the other items in shared get overwriten, if so how to spread them in?
};