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
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
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>)}
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)
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
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