TypeScriptError: Type 'Data' is not assignable to type 'string' - react-redux

I am using React-typescript for my app. for state management I am using Redux-toolkit. I am fetching one open api and store it my redux store. I created dispatch function. From the component when I click the dispatch function then it will display random dog image. But the problem is after mapping the when I am using this img src. I am getting typescript error: Type 'Data' is not assignable to type 'string'. I don't know what I am doing wrong. i uploaded my code in codesandbox, although it works in codesandbox but does not work in my app.
Ps. I did not upload my store setup code because it works find ☺️.
This is my reducer
/* eslint-disable #typescript-eslint/indent */
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { AppThunk } from "store/store";
interface IMeta {
loading: boolean;
error: boolean;
message: string;
}
interface Data {
src: string;
}
interface IDogs {
meta: IMeta;
dogs: Data[];
}
const initialState: IDogs = {
"meta": {
"loading": false,
"error": false,
"message": ``
},
"dogs": []
};
const dogSlice = createSlice({
"name": `random-dogs`,
initialState,
"reducers": {
loadState(state) {
state.meta = {
"loading": true,
"error": false,
"message": ``
};
state.dogs = [];
},
fetchData(state, action: PayloadAction<Data[]>) {
state.meta.loading = false;
state.dogs = action.payload;
console.log(`dogs`, action.payload);
},
loadFailed(state, action: PayloadAction<string>) {
state.meta = {
"loading": false,
"error": true,
"message": action.payload
};
state.dogs = [];
}
}
});
export const { loadState, fetchData, loadFailed } = dogSlice.actions;
export default dogSlice.reducer;
export const fetchDogs = (): AppThunk => async (dispatch) => {
const url = `https://dog.ceo/api/breeds/image/random/5`;
try {
dispatch(loadState);
const response = await fetch(url);
const data = await response.json();
console.log(data);
console.log(data.message);
const singleData = data.message.map((i) => i);
dispatch(fetchData(singleData));
} catch (error) {
dispatch(loadFailed(`dogs are unavailable`));
console.log({ error });
}
};
This is the component I am using the redux store
import React, { memo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchDogs } from 'store/dogs';
import { RootState } from 'store/combineReducer';
export default memo(() => {
const state = useSelector((rootState: RootState) => ({
"dogs": rootState.fetchDogs.dogs,
"meta": rootState.fetchDogs.meta
}));
const dispatch = useDispatch();
console.log(`Dog component`, state.dogs[0]);
return (
<div>
{state.meta.loading ? <p>loading....</p> :
state.dogs.map((i, index) =>
<div key={index}>
<ul>
<li>{i}</li> // I can see the strings
</ul>
<img style={{ "width": 50, "height": 50 }} src={i} /> //getting error in here
</div>)}
<br></br>
<button onClick={() => dispatch(fetchDogs())}> display random dogs</button>
</div>
);
});

The situation is as follows:
Interface IDog is has a property "dogs" of type Data[].
Data has a property "src" of type String.
Src attribute of an img needs to be a string.
You are now passing IDogs.dogs. You need to go deeper to IDogs.dogs.src to get the source string you want.
So line 25 of App.tsx should look like this and all seems to work fine:
<img style={{ width: 50, height: 50 }} src={i.src} alt="dog" />
PS: The codesandbox example still works as it apparently does some kind of assumption that you want the src property, but as you see you still get the error.
EDIT: After some fiddling the answer is as below. It is however connected to what was written above.
I downloaded you project and tried to run in npm on my PC. I did 2 things to make it work:
I updated line 25 to use the cast: src={String(i)}
I updated react-scripts. See this thread for reference: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app

Related

why is an array fetched from backend not in the same order in redux store (react app)?

In my React app, i am fetching an array of posts from a backend api (nodejs/SQL DB).
I am using redux for the frontend, so i thought it would be a good idea to sort the posts on the backend and send them to the frontend (sorted by id, from latest to oldest).
Then, the array of posts gets stored in my redux store.
It's working fine, but i am confused because when i check the store, the posts are not ordered anymore, or rather: the same 4 random posts always get "pushed" to the top and then the rest is ordered as i wanted.
So when i refresh the page i can see these older random posts in the UI at the top of the thread/feed of posts and when component is fully mounted it renders posts in the correct order. Not good.
I wanted to avoid sorting the array of posts on the frontend for performance concerns, am i wrong?
Redux initial state:
const initialState = {
posts: [],
userPosts: [],
currentPost: {
title: "",
text: "",
imgUrl: "",
},
scrapedPost: {},
comments: [],
replies: [],
likes: [],
error: "",
lastPostAdded: null,
lastReplyAdded: null,
lastDeleted: null,
sessionExpired: false,
users: [],
};
Redux root reducer:
import { combineReducers } from "redux";
import { postsReducer } from "./posts.reducer.js";
import { userReducer } from "./user.reducer.js";
export const rootReducer = combineReducers({
user: userReducer,
posts: postsReducer,
});
Redux store config:
import { applyMiddleware, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import { persistReducer, persistStore } from "redux-persist";
import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
import storage from "redux-persist/lib/storage";
import thunk from "redux-thunk";
import { rootReducer } from "./reducers/root.reducer";
const composeEnhancer = composeWithDevTools({ trace: true, traceLimit: 25 });
const persistConfig = {
key: "root",
storage,
stateReconciler: autoMergeLevel2,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer, composeEnhancer(applyMiddleware(thunk)));
const persistor = persistStore(store);
export { store, persistor };
getPost action creator (using thunk middleware for async task):
export const getPosts = () => async (dispatch) => {
const accessToken = localStorage.getItem("jwt");
const request = {
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${accessToken}`,
},
method: "get",
};
try {
const response = await fetch(API_POST, request);
const data = await response.json();
const { posts, likes, sessionExpired } = data;
if (sessionExpired) {
dispatch({ type: SESSION_EXPIRED, payload: sessionExpired });
return;
}
dispatch({ type: GET_POSTS, payload: { posts, likes } });
} catch (error) {
dispatch({ type: SET_ERROR_POST, payload: error.message });
}
}
the posts reducer:
export const postsReducer = (state = initialState, action) => {
switch (action.type) {
case GET_POSTS: {
const { posts, likes } = action.payload;
return { ...state, posts, likes };
}
case GET_LIKES: {
const { likes } = action.payload;
return { ...state, likes };
// all other actions...//
}
relevant part of the UI code (feed component):
const Feed = () => {
const [newUser, setNewUser] = useState(false);
const user = useSelector((state) => state.user);
const { isAuthenticated, isNewUser } = useSelector((state) => state.user);
const posts = useSelector((state) => state.posts.posts);
const dispatch = useDispatch();
const userLanguage = useLanguage();
useEffect(() => {
window.scrollTo(0, 0);
setNewUser(isNewUser);
return function cleanup() {
setNewUser(null);
};
}, [isNewUser]);
useEffect(() => {
dispatch(getPosts());
}, []);
return (
<Layout>
//some jsx...//
<button className="h-6 refreshBtn outline-none hover:cursor-pointer bg-blue-500
text-white rounded-full gap-1 flex items-center justify-center pl-2 pr-3 py-1
shadow transition-all duration-300 hover:bg-black hover:shadow-none group"
onClick={() => dispatch(getPosts())}
style={{ opacity: posts && posts.length !== 0 ? 1 : 0 }}>
<RefreshIcon className="h-4 w-4 pointer-events-auto transform transition
transform duration-500 group-hover:-rotate-180" />
<span className="text-xs pointer-events-auto capitalize">
{userLanguage?.feed.refreshBtn}</span>
</button>
<div className="posts-wrapper h-full w-full relative flex flex-col items-center
justify-center gap-4 pb-6">
{posts.length === 0
? (<Skeleton element="post" number={8} />)
: (posts.map((post) => <Post key={post.postId} post={post} />)}
</div>
</Layout>
};
posts ordered by Id on the backend:
screenshot
posts in the redux store (as you can see by their postId, indexes 0 to 3 have nothing to do there)
screenshot
so my questions:
how come the array fetched is not in the same order in redux store?
why does the UI flash the "wrong" order for a sec, then the correct order? how does it know the correct order if those 4 posts are still at the top in the store?
i'm confused here, any hint or help is appreciated! thanks
I finally found the solution months ago but forgot to come back here to give the solution to the issue i had.
Turns out the order of the posts fetched from backend wasn't modified or messed up with by Redux at all but by me (of course!) from another component called PopularPosts.
Consider the code below:
const PopularPosts = () => {
const { posts } = useSelector(state => state.posts);
const [top3, setTop3] = useState<IPost[]>([]);
useEffect(() => {
setTop3(posts.sort((a, b) => { my sorting logic }).splice(0, 3));
}, [posts]);
I was literally mutating the store directly in order to create my top3. Of course this was a HUGE mistake! I should have used the sort() method on a COPY of the store, not the store itself.
Here is the correct code:
const PopularPosts = () => {
const { posts } = useSelector(state => state.posts);
const [top3, setTop3] = useState<IPost[]>([]);
useEffect(() => {
const postsCopy = [...posts];
setTop3(postsCopy.sort((a, b) => { // my sorting logic }).splice(0, 3));
}, [posts]);
All is working as intended since this correction.
And lesson learnt: i'll never mutate the Redux store directly ever again ;)

Network Request Failed Error React Native, using Flask Python as BackEnd

Using postman the backend seems to be getting the correct data back but when I throw it into a fetch the components are not re-rendering the state and also giving me a yellow error, of Network Request Failed.
The backend is made of Flask Python, and the FrontEnd is using Expo CLI with an android emulator, I looked up previous issues and found some saying the emulator and the machine are not connected, I fixed that issue by doing a remote/local ip ex: 192.168.1.... but it's still returning a network request failed.
import React, { useEffect, useState } from "react";
import { ScrollView, View, TouchableOpacity, Text } from "react-native";
import { connect } from "react-redux";
import * as actions from "../src/actions";
import EarningsBar from "./EarningsBar";
const DayCalendar = (props) => {
const [isLoaded, setisLoaded] = useState(false);
useEffect(() => {
props.loadStock("daily");
if (Object.keys(props.dailyStocks).length > 0) {
setisLoaded(true);
console.log(props.dailyStocks);
}
}, []);
return (
<ScrollView nestedScrollEnabled={true}>
{isLoaded === true &&
props.dailyStocks.time.map(
({ companyshortname, epsactual, epsestimate, ticker }, index) => {
return (
<EarningsBar
key={index}
companyName={companyshortname}
companyAbbrev={ticker}
companyEPS={epsestimate}
companyRev={"$3.28"}
companyActualEPS={epsactual}
companyActualRES={"$5.66"}
companyGrowthEPS={"103.3%"}
companyGrowthRev={"83.8%"}
arrow={"good"}
/>
);
}
)}
</ScrollView>
);
};
const mapStateToProps = (state) => {
return {
dailyStocks: state.calendar.daily,
};
};
export default connect(mapStateToProps, actions)(DayCalendar);
This is the day calendar Component
Here is the action function
export const loadStock = (stock) => {
return (dispatch) => {
try {
fetch(`http://192.168.1.13:3000/${stock}stock`)
.then((response) => {
return response.json();
})
.then((data) => {
dispatch({ type: "LOAD_STOCKS", payload: { stock, data } });
});
} catch (error) {
console.log(error);
}
};
};
My reducer states/and switch cases
export const initialLoginState = {
users: [],
searchedStocks: [],
calendar: {
daily: [],
weekly: [],
monthly: [],
},
stocks: [],
searchTerm: null,
isLoading: true,
user_id: null,
username: null,
password: null,
confirmPassword: null,
email: null,
birthdate: null,
question: null,
answer: null,
userToken: null,
isValidUser: true,
isValidPassword: true,
};
export default (prevState = initialLoginState, action) => {
switch (action.type) {
case "LOAD_STOCKS":
return {
...prevState,
calendar: {
[action.payload.prop]: action.payload.value,
},
};
default:
return {
...prevState,
};
}
};
If you would like to see the full repo I have linked it down below. I have been trying to figure out the issue for quite some time now and I just need a new perspective on it, thank you so much for your time.
https://github.com/JayPankajPatel/EarningsWhisper
Open CMD and run ipconfig, then copy your ip address there
In your flask code, let say app.py
Change app.run() to
app.run(host=your ip address)

TypeError: Cannot read property 'type' of undefined (redux toolkit)

I'm trying to fetch some data with redux toolkit but it doesn't work. I just keep getting the error TypeError: Cannot read property 'type' of undefined. I set up the store correct because i have other reducer working fine. But when i tried the asyn or fetch data, i have this problem
Error:
App.js:
The code stop at const actionResult = await dispath(getLiveContest()) it doesn't console log anything after.
const dispatch = useDispatch();
useEffect(() => {
const fetchLiveContest = async () => {
try {
console.log(1);
const actionResult = await dispatch(getLiveContest());
console.log(2);
const liveContest = unwrapResult(actionResult);
console.log(liveContest);
} catch (error) {
console.log("Failed to fetch live contest: ", error);
}
};
fetchLiveContest();
}, []);
GetLiveContest():
Here is the code of the function. I tried to return {name: 'lala'} and it's still gave me the type error
export const getLiveContest = createAsyncThunk(
"contests/fetchLive",
async (params, thunkAPI) => {
console.log(thunkAPI, "thunkAPI");
console.log(params);
const liveContest = await axios ...
return liveContest;
}
);
Code of the slide:
export const liveContestSlide = createSlice({
name: "live",
initialState: {
contest: [],
loading: "idle",
},
reducers: {},
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[getLiveContest.fulfilled]: (state, action) => {
// Add contest to the state array
state.contest.push(action.payload);
},
},
});
I followed the redux toolkit doc. I also checkout other question on stackoverflow but still can't fix the error, pls help
I just change import getLiveContest from "./contestSlice"; to import { getLiveContest } from "./contestSlice"; and it work, turn out i just import the function wrong

useState depending on other state

I have this useSiren hook that should update its state with the incoming json argument but it doesnt.
On the first call the json is an empty object, because the fetch effect has not been run yet.
On the second call its also an empty object (triggered by loading getting set to true in App)
And on the third call its filled with valid data. However, the valid data is not applied. The state keeps its initial value.
I guess somehow setSiren must be called to update it, since initial state can only be set once. But how would I do that? Who should call `setSiren?
import { h, render } from 'https://unpkg.com/preact#latest?module';
import { useEffect, useState, useCallback } from 'https://unpkg.com/preact#latest/hooks/dist/hooks.module.js?module';
import htm from "https://unpkg.com/htm#latest/dist/htm.module.js?module";
const html = htm.bind(h);
function useFetch({
method = "GET",
autoFetch = true,
href,
body
}) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState()
const [response, setResponse] = useState()
const [isCancelled, cancel] = useState()
const [json, setJson] = useState({})
const sendRequest = async payload => {
try {
setLoading(true)
setError(undefined)
const response = await fetch(href.replace("http://", "https://"), {
method
})
const json = await response.json()
if (!isCancelled) {
setJson(json)
setResponse(response)
}
return json
} catch (err) {
if (!isCancelled) {
setError(err)
}
throw err
} finally {
setLoading(false)
}
}
if (autoFetch) {
useEffect(() => {
sendRequest(body)
return () => cancel(true)
}, [])
}
return [{
loading,
response,
error,
json
},
sendRequest
]
}
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
function Action(props) {
const [{ loading, error, json }, sendRequest] = useFetch({ autoFetch: false, href: props.href, method: props.method })
const requestAndUpdate = () => {
sendRequest().then(props.onRefresh)
}
return (
html`
<button disabled=${loading} onClick=${requestAndUpdate}>
${props.title}
</button>
`
)
}
function App() {
const [{ loading, json }, sendRequest] = useFetch({ href: "https://restlr.io/toggle/0" })
const [{ state, actions }, setSiren] = useSiren(json)
return (
html`<div>
<div>State: ${loading ? "Loading..." : (state.properties && state.properties.value)}</div>
${actions.map(action => html`<${Action} href=${action.href} title=${action.title || action.name} method=${action.method} onRefresh=${setSiren}/>`)}
<button disabled=${loading} onClick=${sendRequest}>
REFRESH
</button>
</div>
`
);
}
render(html`<${App}/>`, document.body)
Maybe what you want to do is to update the siren state when the json param changes? You can use a useEffect to automatically update it.
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
useEffect(() => { // here
setSiren(json)
}, [json])
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
The pattern mentioned by #awmleer is packaged in use-selector:
import { useSelectorValue } from 'use-selector';
const { entities=[], actions=[], title} = json;
const siren = useSelectorValue(() => ({
state: entities.find(entity => entity.class === 'state') || {},
actions,
title
}), [json]);
Disclosure I'm author and maintainer of use-selector

Error TypeError: Cannot read property 'dispatch' of undefined at app.js:12012

Hi I've been trying to learn vuejs and vuex while trying to get response from an api call with vuex concept I got the following error.Please help.
This error occurred
Error TypeError: Cannot read property 'dispatch' of undefined
at app.js:12012
loginAction.js
export const getUsersList = function (store) {
let url = '/Apis/allUsers';
Vue.http.get(url).then((response) => {
store.dispatch('GET_USER_RES', response.data);
if (response.status == 200) {
}
}).catch((response) => {
console.log('Error', response)
})
}
loginStore.js
const state = {
userResponse: []
}
const mutations = {
GET_USER_RES (state, userResponse) {
state.userResponse = userResponse;
}
}
export default {
state, mutations
}
login.vue
import {getUsersList} from './loginAction';
export default {
created () {
try{
getUsersList();
}catch(e){
console.log(e);
}
},
vuex: {
getters: {
getUsersList: state => state.userResponse
},
actions: {
getUsersList
}
}
}
</ script>
If you call the actions manually (like in your try/catch) they'll not get the store context as the first argument. You could use getUsersList(this.store) I think, but instead I would use dispatch to reach all your actions. (I edited just a little bit to get a minimal running example, but I think you get the point!)
new Vue({
render: h => h(App),
created() {
this.$store.dispatch('getUsersList');
},
store: new Vuex.Store({
getters: {
getUsersList: state => state.userResponse
},
actions: {
getUsersList
}
})
}).$mount("#app");
Also, use commit to reach the mutations instead of dispatch. ie:
export const getUsersList = function ({commit}) {
let url = '/Apis/allUsers';
Vue.http.get(url).then((response) => {
commit('GET_USER_RES', response.data); // because GET_USER_RES is a mutation
...

Resources