Converting React useReducer to TypeScript - reactjs

I am trying to convert a useReducer hooks to TypeScript,
It's working fine as useReducer hooks, however not in TypeScript.
Here is my code,
import * as React from "react";
const JOKE_URL = "https://icanhazdadjoke.com/";
const initialState = { data: null, error: null, loading: true };
type ACTIONTYPE =
| { type: "fetch" }
| { type: "data"; data: object }
| {type: "error"};
function fetchReducer(state: typeof initialState, action: ACTIONTYPE) {
switch (action.type) {
case 'fetch':
return {
...state,
loading: true,
}
case 'data':
return {
...state,
data: action.data,
error: null,
loading: false,
}
case 'error':
return {
...state,
error: 'Error fetching data. Try again',
loading: false,
}
default:
return state;
}
}
function useFetch (url: string) {
const [state, dispatch] = React.useReducer(
fetchReducer,
{ data: null, error: null, loading: true }
)
React.useEffect(() => {
dispatch({ type: 'fetch' })
fetch(url, {
headers: {
accept: "application/json"
}
})
.then((res) => res.json())
.then((data) => dispatch({ type: 'data', data }))
.catch((e) => {
console.warn(e.message)
dispatch({ type: 'error' })
})
}, [url])
return {
loading: state.loading,
data: state.data,
error: state.error
}
}
export default function App() {
const { loading, data, error } = useFetch(JOKE_URL);
console.log(data);
if (loading === true) {
return <p>Loading</p>
}
if (error) {
return (
<React.Fragment>
<p>{error}</p>
</React.Fragment>
)
}
return (
<div>
<h1>{data.joke}</h1>
</div>
);
}
I am getting some errors like:
-> Argument of type '(state: { data: null; error: null; loading: boolean; }, action: ACTIONTYPE) => { data: null; error: null; loading: boolean; } | { data: object; error: null; loading: boolean; } | { error: string; loading: boolean; data: null; }' is not assignable to parameter of type 'ReducerWithoutAction'. TS2769
-> Expected 0 arguments, but got 1. TS2554

You should set return type for your reducer and all working fine , also i did change some of your state and types for cleaning your code :
import * as React from "react";
const JOKE_URL = "https://icanhazdadjoke.com/";
const initialState = { loading: true };
type initState ={
data?: any,
error?: string,
loading: boolean
}
type ACTIONTYPE =
| { type: "fetch" }
| { type: "data"; data: object }
| { type: "error"};
function fetchReducer(state: initState, action: ACTIONTYPE):initState {
switch (action.type) {
case 'fetch':
return {
...state,
loading: true,
}
case 'data':
return {
...state,
data: action.data,
loading: false,
}
case 'error':
return {
...state,
error: 'Error fetching data. Try again',
loading: false,
}
default:
return state;
}
}
function useFetch (url: string) {
const [state, dispatch] = React.useReducer(
fetchReducer,
initialState
)
React.useEffect(() => {
dispatch({ type: 'fetch' })
fetch(url, {
headers: {
accept: "application/json"
}
})
.then((res) => res.json())
.then((data) => dispatch({ type: 'data', data }))
.catch((e) => {
console.warn(e.message)
dispatch({ type: 'error' })
})
}, [url])
return {
loading: state.loading,
data: state.data,
error: state.error
}
}
export default function App() {
const { loading, data, error } = useFetch(JOKE_URL);
console.log(data);
if (loading) {
return <p>Loading</p>
}
if (error) {
return (
<React.Fragment>
<p>{error}</p>
</React.Fragment>
)
}
return (
<div>
<h1>{data.joke}</h1>
</div>
);
}

Related

React strict mode causes fetch abort

i made a custom hook for fetching data the problem is when i use <React.StrictMode> the fetch singal for aborting gets fire but some how it works if i remove strict mode
this is the fetch hook
import { useEffect, useReducer } from 'react';
import { ApiResponse } from '../interfaces/ApiResponse';
const initialState: ApiResponse = {
loading: false,
data: null,
error: null,
};
type Action =
| { type: 'start' }
| { type: 'error'; payload: Error }
| { type: 'success'; payload: JSON };
const reducer = (state: ApiResponse, action: Action) => {
switch (action.type) {
case 'start':
return {
loading: true,
data: null,
error: null,
};
case 'success':
return {
loading: false,
data: action.payload,
error: null,
};
case 'error':
return {
loading: false,
data: null,
error: action.payload,
};
default:
return state;
}
};
export const useFetch = (url: string): ApiResponse => {
const [response, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const controller: AbortController = new AbortController();
const signal: AbortSignal = controller.signal;
const fetchData = async () => {
dispatch({ type: 'start' });
try {
const response: Response = await fetch(url, { signal: signal });
if (response.ok) {
const json = await response.json();
dispatch({
type: 'success',
payload: json,
});
} else {
dispatch({
type: 'error',
payload: new Error(response.statusText),
});
}
} catch (error: any) {
dispatch({
type: 'error',
payload: new Error(error),
});
}
};
fetchData();
return () => {
controller.abort();
};
}, [url]);
return response;
};
when i call this hook in one of my components like this:
const Grid = () => {
const response = useFetch(`${BASE_API_URL}/games`);
useEffect(() => {
console.log(response);
}, [response]);
return (
<div className='grid__wrapper'>
<div className='grid__content'>
{response.loading && <h4>Loading...</h4>}
<h4>helo</h4>
</div>
</div>
);
};
export default Grid;
the response.loading is never set to true and i can see an abort error in the logs but if i remove strict mode it works fine

Get collection length from mongodb in reactjs redux

Here's the error that the response looks like an empty array not a number "the length"
I want to display collection length from mongodb in react with redux.
Here's my code:
Action:
carActions.js
export const howMany = () => async (dispatch) => {
try {
dispatch({ type: CAR_NUMBER_REQUEST })
const { data } = await axios.get('/api/cars')
dispatch({ type: CAR_NUMBER_SUCCESS, data })
} catch (error) {
dispatch({
type: CAR_NUMBER_FAIL, payload: error.message})
}
}
Reducer:
carReducer.js
export const carNumberReducer = (state = { cars: [] }, action) => {
switch (action.type) {
case CAR_NUMBER_REQUEST:
return { loading: true, cars: [] }
case CAR_NUMBER_SUCCESS:
return { loading: false, success: true, cars: action.payload }
case CAR_NUMBER_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
Here where it would be displayed:
carScreen.js
const dispatch = useDispatch()
const carNumber = useSelector(state => state.carNumber)
const { cars } = carNumber
useEffect(() => {
dispatch(howMany());
}, [dispatch])
return (
<div>
{cars.map((cars) => (
<p key={cars._id}>{cars}</p>
))}
</div>)}

How do i fix this ? i am getting this error 'TypeError: Cannot read property 'map' of undefined' i am trying to access the data from the actions file

Here is the list Orders file. this is the code i have created so far But when i try to map throught the orders i am reveining that error and i dont know why. When i use Postman to try and get the orders, it works pefectly fine which means im either missing a code or got an error somewhere within these codes. Does anyone have an idea or could see where the porbl;em is ? So the error i am getting is as follow
'TypeError: Cannot read property 'map' of undefined'
const { orders, loading, error } = useSelector(state => state.personalUserOrders)
const dispatch = useDispatch()
useEffect(() => {
dispatch(personalORders())
}, [ dispatch])
return (
<div>
<table className='tables'>
<thead>
<tr>
<th>Id</th>
<th>Aount</th>
</tr>
</thead>
<tbody>
{ orders.map((order) => (
<tr key={order._id}>
<td>{order._id}</td>
<td>{order.sumPrice}</td>
</tr>
))
}
</tbody>
</table>
</div>
)
}
export default OrdersList
OrdersActions file
import axios from 'axios'
import {
REQUEST_CREATE_ORDER, SUCCES_CREATE_ORDER, FAIL_CREATE_ORDER, CLEAR_ERRORS,
REQUEST_PERSONAL_ORDER, SUCCESS_PERSONAL_ORDER,FAIL_PERSONAL_ORDER
} from '../constants/orderConstant'
export const createOrders = (orders) => async (dispatch, getState) => {
try {
dispatch({
type: REQUEST_CREATE_ORDER
})
const config = {
headers: {
'Content-Type': 'application/json'
}
}
const { data } = await axios.post(`/api/orders/newOrder`, orders, config)
console.log('Datttttta',{data})
dispatch({
type: SUCCES_CREATE_ORDER,
payload: data
})
} catch (error) {
dispatch({
type: FAIL_CREATE_ORDER,
payload: error.response.data.message
})
}
}
export const personalORders = () => async (dispatch) => {
dispatch({
type:REQUEST_PERSONAL_ORDER
})
try {
const { data } = await axios.get('/api/orders/personalOrders')
dispatch({
type: SUCCESS_PERSONAL_ORDER,
payload: data
})
} catch (error) {
dispatch({
type: FAIL_PERSONAL_ORDER,
payload: error.response.data.message
})
}
}
export const clearError=()=> async (dispatch)=>{
dispatch({
type: CLEAR_ERRORS
})
}
Orders Reducer File
import {
REQUEST_CREATE_ORDER,
SUCCES_CREATE_ORDER,
FAIL_CREATE_ORDER,
CLEAR_ERRORS,
REQUEST_PERSONAL_ORDER,
SUCCESS_PERSONAL_ORDER,
FAIL_PERSONAL_ORDER
} from '../constants/orderConstant'
export const createNewOrderReducter = (state = {}, action) => {
switch (action.type) {
case REQUEST_CREATE_ORDER:
return {
...state,
loading: true
}
case SUCCES_CREATE_ORDER:
return {
loading: false,
orders: action.payload
}
case FAIL_CREATE_ORDER:
return {
loading: false,
error: action.payload
}
case CLEAR_ERRORS:
return {
...state,
error: null
}
default:
return state
}
}
export const personalOrdersReducer = (state = { orders: [] }, action) => {
switch (action.type) {
case REQUEST_PERSONAL_ORDER:
return {
loading: true
}
case SUCCESS_PERSONAL_ORDER:
return {
loading: false,
orders: action.payload
}
case FAIL_PERSONAL_ORDER:
return {
loading: false,
error: action.payload
}
case CLEAR_ERRORS:
return {
...state,
error: null
}
default:
return state
}
}
You try to get personalUserOrders from store, however i can see just orders in your reducer.
Also you try to destruct full state object, from state.personalUserOrders
Try to use this
const { orders, loading, error } = useSelector(state => state)

React, Typescript, Hooks - destructuring data from hook, TS2339: Property 'data' does not exist on type

I have problem with destructuring Typescript data object from my own Hook created in React.
export interface InitialState {
pokemonListLoading: false;
pokemonListLoadingFailed: false;
data: [];
}
interface FetchPokemonList {
type: typeof FETCH_POKEMON_LIST;
}
interface FetchPokemonListSuccess {
type: typeof FETCH_POKEMON_LIST_SUCCESS;
payload: PokemonList;
}
...
export type PokemonListActionTypes = FetchPokemonList | FetchPokemonListSuccess | FetchPokemonListError;
const dataFetchReducer = (state: InitialState, action: PokemonListActionTypes) => {
switch (action.type) {
case FETCH_POKEMON_LIST:
return {
...state,
pokemonListLoading: true,
pokemonListLoadingFailed: false,
};
case FETCH_POKEMON_LIST_SUCCESS:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: action.payload,
};
case FETCH_POKEMON_LIST_ERROR:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: true,
};
default:
throw new Error();
}
};
export const fetchPokemonList = (initialUrl: string, initialData: []) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: initialData,
});
useEffect(() => {
const fetchData = async () => {
dispatch({ type: FETCH_POKEMON_LIST });
try {
const result = await axios(url);
dispatch({ type: FETCH_POKEMON_LIST_SUCCESS, payload: result.data });
} catch (error) {
dispatch({ type: FETCH_POKEMON_LIST_ERROR });
}
};
fetchData();
}, [url]);
return [state, setUrl];
};
and whole component
import React, { FunctionComponent } from 'react';
import { fetchPokemonList, InitialState } from '../../hooks/fetchPokemonList';
const PokemonList: FunctionComponent = () => {
const [{
data: { results: pokemonList },
pokemonListLoading,
pokemonListLoadingFailed,
},
] = fetchPokemonList('https://pokeapi.co/api/v2/pokemon',[]);
return (
<div>
PokemonList
{pokemonListLoading ? (
<div>Laoding...</div>
) : (
pokemonList && pokemonList.map((pokemon: { name: string}) => (
<div key={pokemon.name}>{pokemon.name}</div>
))
)}
{pokemonListLoadingFailed && <div>Error</div>}
</div>
)
}
export { PokemonList }
error code displayed by Webstorm
TS2339: Property 'data' does not exist on type '{ pokemonListLoading:
boolean; pokemonListLoadingFailed: boolean; data: []; } | {
pokemonListLoading: boolean; pokemonListLoadingFailed: boolean; data:
PokemonList; } | Dispatch ...>>'.
The issue is within this line:
dispatch({ type: FETCH_POKEMON_LIST_SUCCESS, payload: result.data });
Where you send as payload without using a key for your new data value.
Then in the code section you're setting data with the payload object, which results in the error you're experiencing:
case FETCH_POKEMON_LIST_SUCCESS:
return {
...state,
pokemonListLoading: false,
pokemonListLoadingFailed: false,
data: action.payload,
};
Try passing your payload like this: payload: { data: result.data }.
Then set your data respectively: data: action.payload.data

Reducer updates state object which it should not be able to access

The problem is that when on of my reducer updates its own state, it also updates the state of another reducer.
//authActions.js
export const authActions = {
login: (props) => dispatch => {
// This will make sure the loading spinner will appear.
dispatch({
type: POST_LOGIN_PENDING,
payload: null
})
// make request to login user
axios.post(LOGIN_ENDPOINT, {
email: props.email,
password: props.password
}).then(res => dispatch({
type: POST_LOGIN_FULFILLED,
payload: res.data
})
).catch( () => dispatch({
type: POST_LOGIN_REJECTED,
payload: null
}))
},
logout: () => dispatch => {
dispatch({
type: LOGOUT,
payload: null
})
},
// authReducer.js
export const initialState = {
token: "",
userRole: "",
isLoading: false,
loginFailed: false,
isAuthenticated: false,
}
export function authReducer(state = initialState, action) {
switch (action.type) {
case POST_LOGIN_PENDING:
return {
...state,
isLoading: true,
}
case POST_LOGIN_FULFILLED:
return {
...state,
token: action.payload.token,
userRole: action.payload.userRole,
loginFailed: false,
isAuthenticated: true,
isLoading: false,
}
case POST_LOGIN_REJECTED:
return {
...state,
loginFailed: true,
isLoading: false,
}
// studentActions.js
export const studentActions = {
getAllStudents: props => dispatch => {
dispatch({
type: GET_ALL_STUDENTS_PENDING,
payload: null,
})
axios.get(STUDENTS_ENDPOINT, {
headers: {
'Authorization': `Bearer ${props.token}`
}
})
.then(res =>
dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: res.data
}))
.catch(err => dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: err
}))
},
// studentReducer.js
export const initialState = {
students: [],
err: "",
isLoading: false,
}
export function studentReducer(state = initialState, action) {
switch (action.type) {
case GET_ALL_STUDENTS_PENDING:
return {
...state,
isLoading: true,
}
case GET_ALL_STUDENTS_FULFILLED:
return {
...state,
students: action.payload,
isLoading: false,
}
case GET_ALL_STUDENTS_REJECTED:
return {
...state,
err: action.payload,
isLoading: false,
}
case DELETE_STUDENT_BY_ID_FULFILLED:
return state
default:
return state
}
}
When a user logs in and the POST_LOGIN_FULFILLED applies. I would expect only the initialstate of the authReducer to be updated, but when inspect with the redux devtools I can see that that the array "studens" which is part of the initialstate of the studentReducer also is updated. From what I understand this should not be possible.
After the user has logged in the students array is filled: (From redux devtools)
student: {
students: [] => {....some stuff}
isLoading: true => false
}
By reading you comments it looks like that GET_ALL_STUDENTS_FULFILLED refers to POST_LOGIN_FULFILLED . This must be the reason why your students array is updated. Change
export const GET_ALL_STUDENTS_PENDING = 'POST_LOGIN_PENDING';
export const GET_ALL_STUDENTS_REJECTED = 'POST_LOGIN_REJECTED';
export const GET_ALL_STUDENTS_FULFILLED = 'POST_LOGIN_FULFILLED';
to
export const GET_ALL_STUDENTS_PENDING = 'GET_ALL_STUDENTS_PENDING ';
export const GET_ALL_STUDENTS_REJECTED = 'GET_ALL_STUDENTS_REJECTED ';
export const GET_ALL_STUDENTS_FULFILLED = 'GET_ALL_STUDENTS_FULFILLED ';
Action types should be unique or else it might get triggered by some other action

Resources