I'm using apisauce to create api for my app. And apisauce had a function call addMonitor, here: https://github.com/infinitered/apisauce#adding-monitors.
But when I add useDispatch to naviMonitor, the code behind cannot run. Specifically, I can't log the response. Bellow is my Api.js:
import apisauce from 'apisauce';
import {useDispatch} from 'react-redux';
const create = () => {
...
const naviMonitor = (response) => {
const dispatch = useDispatch();
const {status} = response || {};
if ( (status && status === 200) ) {
console.log(response);
} else {
// TODO
}
};
api.addMonitor(naviMonitor);
}
Where was I wrong?
Thank you for help me.
Have a try by returning the response object from API to the component function and use the dispatch method there as we are not allowed to use the useDispatch hook or any react hooks outside the component.
Like :
import apisauce from 'apisauce';
import {useDispatch} from 'react-redux';
const create = () => {
...
const naviMonitor = (response) => {
const {status} = response || {};
if ( (status && status === 200) ) {
console.log(response);
return response; // process response if required.
} else {
// TODO
return response; // process response if required.
}
};
api.addMonitor(naviMonitor);
}
In The component screen:
import {useDispatch} from 'react-redux';
const MyComponent = props => {
const dispatch = useDispatch();
const actionHandler = async () => {
const response = await create();
dispatch(response); // dispatch your actions here..
}
}
Related
I am trying to pass a variable value which uses useParam hook so i can pass it to my api which set outside of the component function.
VesselComponent.js :
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchComponents } from "../../../features/componentsSlice";
import TreeItem from "#mui/lab/TreeItem";
import TreeView from "#mui/lab/TreeView";
import ExpandMoreIcon from "#mui/icons-material/ExpandMore";
import ChevronRightIcon from "#mui/icons-material/ChevronRight";
import { Link, Outlet, useParams } from "react-router-dom";
import axios from "axios";
export const api = async () => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
function VesselComponents() {
// this line
const vesselId = useParams();
const { components, error, loading } = useSelector(
(state) => state.components
);
// rest of the code
You can try to pass a param to api that would help you have vesselId from other places including useParams
export const api = async (vesselId) => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
Here is how we call it
const vesselId = useParams();
api(vesselId);
You can only use react hooks at the top level inside a component. You shouldn't call useParams in your api function. Instead, you should pass it to your api function and use some state to store the response from your API. Something like this:
export const api = async (vesselId) => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
function VesselComponents() {
// this line
const vesselId = useParams();
const [vesselData, setVesselData] = useState();
const { components, error, loading } = useSelector(
(state) => state.components
);
const fetchVesselData = async () => {
try {
const res = await api(vesselId);
setVessesData(res);
} catch (e) {
// handle error
}
}
useEffect(() => {
fetchVesselData()
});
I want to fetch data from an API and then print it out or to display it in the return statement from my react compenent so I can have a p element that have data fetched from the api in it.
The problem is that the usestate dont get updated
The component code
import React, { useEffect } from "react"
import { newsComponentService } from "../services/newsComponentService";
const NewsComponent = () => {
const [newsComponentData, setNewsComponentData] = React.useState({});
const componentData = React.useRef("");
async function newsComponentHandler() {
let res = await newsComponentService();
//console.log(res);
setNewsComponentData(res);
//console.log(res);
}
useEffect(() => {
newsComponentHandler();
//setNewsComponentData(res);
}, [setNewsComponentData]);
console.log(newsComponentData["data"]);
return (
<p>{newsComponentData["data"]}</p>
)
}
export default NewsComponent;
The api service code
export async function newsComponentService(){
const response = await fetch("api/news-categories/1", {
method: 'GET',
headers: {
'Accept': 'application/json',
},
});
let resJson = await response.json();
//console.log(resJson);
return resJson;
}
I think the issue could be with the async behavior of the JS.
import React, { useEffect } from "react"
import { newsComponentService } from "../services/newsComponentService";
const NewsComponent = () => {
const [newsComponentData, setNewsComponentData] = React.useState({});
const componentData = React.useRef("");
useEffect(() => {
const newsComponentHandler = async () => {
let res = await newsComponentService();
setNewsComponentData(res);
}
newsComponentHandler();
}, [setNewsComponentData]);
console.log(newsComponentData["data"]);
return (
<p>{newsComponentData["data"]}</p>
)
}
export default NewsComponent;
PS. As a good practice, please put the API fetching in try-catch block in newsComponentService.js
When I open the app, useEffect successfully transfers the data to my app via fetch function and the app re-renders so that the todos are displayed.
If I create a new todo via handleCreateTodo, send it to the server via a post request and then call the fetch function, the database is updated but the app does not Re-Render. I have to refresh the browser manually so that the new todo is displayed.
import { FunctionComponent, ReactElement, ChangeEvent, MouseEvent, useState, useEffect } from "react";
import { v4 } from "uuid";
import axios from "axios";
const endpoint = 'http://localhost:8000';
let currentTodos: Todos [];
export const TodoTable: FunctionComponent = (): ReactElement => {
const [todos, setTodos] = useState<Array<Todos>>([]);
const [enterTodo, setEnterTodo] = useState<string>('');
//Get Todos from DB
const fetchTodos = async() => {
const { data, status } = await axios.get(endpoint + '/todos');
if(status == 200){
setTodos(data);
}
}
useEffect(() => {
fetchTodos();
}, []);
//Enter Todo handler
const handleEnterTodo = (event: ChangeEvent<HTMLInputElement>): void => {
setEnterTodo(event.currentTarget.value);
};
//Create Todo handler
const handleCreateTodo = async () => {
//create new Todo
const newTodo = {
//id: todos.length+1,
id: v4(),
describtion: enterTodo,
done: false
};
const { status } = await axios.post(endpoint + '/todos', newTodo);
if(status == 200){
fetchTodos();
}
setEnterTodo('');
};
return(
<>
<div className="todoTable">
<InputBar
enterTodo={ enterTodo }
handleEnterTodo={ handleEnterTodo }
handleCreateTodo={ handleCreateTodo }
handleClearTodos= { handleClearTodos }
/>
<TodosDisplay
todos={ todos }
handleDeleteTodo={ handleDeleteTodo }
handleStatus={ handleStatus }
/>
</div>
</>
);
}
useEffect(() => {
},[todos])
If useEffect take parameter on this array, it will render when todos updated.
I have been trying to use a cleanup function to cancel the API call I a user presses the back button before the request is resolved.
However I still receive the same error "Warning: Can't perform a React state update on an unmounted component.".
I am using fetch function, I added the abortController but still I receive the same warning.
import React, { useState, useEffect, useReducer, useContext } from "react";
import { ActivityIndicator } from "react-native";
import AllThumbnails from "../components/AllThumbnails";
import reducer from "../functions/reducer";
import { lightColors, darkColors } from "../constants/Colors";
import { ThemeContext } from "../context/context";
import ScreenContainer from "../components/UI/ScreenContainer";
export default function AllCatScreen(props) {
const { navigation, route } = props;
const [categories, setCategories] = useState([]);
const [state, dispatch] = useReducer(reducer, { catPage: 1 });
const [theme] = useContext(ThemeContext);
const { taxonomy } = route.params;
useEffect(() => {
const abortCtrl = new AbortController();
const opts = { signal: abortCtrl.signal };
let isActive = true;
fetch(`${siteURL}/wp-json/wp/v2/${taxonomy.endPoint}`, opts)
.then((response) => response.json())
.then((res) => {
if (isActive) {
setCategories([...categories, ...res]);
}
})
.catch((err) => console.log(err));
return function cleanup() {
isActive = false;
console.log(isActive);
abortCtrl.abort();
};
}, []);
if (categories.length == 0) {
return (
<ScreenContainer notYet={true}>
<ActivityIndicator size="large" color={theme.colors.text} />
</ScreenContainer>
);
} else {
return (
<ScreenContainer notYet={false}>
<AllThumbnails
data={categories}
navigation={navigation}
catThumb={true}
action={[state, dispatch]}
fetchData={fetchData}
/>
</ScreenContainer>
);
}
}
I have read that react native should support the AbortController. I am using Expo SDK 38 but even in the clean up function logging the console doesn't work. Does anyone know what's wrong?
I want to test if the loginReset() function is being called every time there's an unauthorized request or response status code 401.
My code is what follows:
use-request.js
import axios from "axios"
import { axiosDefaultOptions } from "../config"
import { useSelector, useDispatch } from "react-redux"
import { loginReset } from "../store/reducers/login-slice"
const useRequest = (auth=false) => {
const request = axios.create(axiosDefaultOptions)
const dispatch = useDispatch()
if(auth){
const token = useSelector( state => state.login.data ? state.login.data.accessToken : null )
request.interceptors.request.use(config => {
config.headers.Authorization = token ? `Bearer ${token}` : ''
return config
})
request.interceptors.response.use(response => {
return response
}, error => {
if(error.response.status === 401) {
dispatch(loginReset())
}
return Promise.reject(error)
})
}
return request
}
export default useRequest
use-request.test.js
import { testHookwithStore } from "../utils"
import faker from "faker"
import { useRequest } from "../../components/hooks"
import configureStore from "redux-mock-store"
import MockAdapter from "axios-mock-adapter"
import { axiosDefaultOptions } from "../../components/config"
import thunk from "redux-thunk"
describe("useRequest", () => {
faker.seed(123);
let request = null
let authRequest = null
let token = faker.random.uuid()
const mockStore = configureStore([thunk])
let authRequestAdapter = null
const fakeDomainWord = faker.internet.domainWord()
const fakeUrl = `${axiosDefaultOptions.baseURL}/${fakeDomainWord}`
beforeEach(() => {
let store = mockStore({
login: { data: { accessToken: token } }
})
testHookwithStore(store, () => {
request = useRequest()
authRequest = useRequest(true)
authRequestAdapter = new MockAdapter(authRequest)
authRequestAdapter.onPost(fakeDomainWord, {}).reply(401, { code: 401, message: "Bad credentials" })
})
})
test("Request should have no headers", () => {
request.interceptors.request.use( config => {
expect(config.headers.Authorization).toBeNull()
})
})
test("Auth request should have Authentication Headers", () => {
authRequest.interceptors.request.use( config => {
expect(config.headers.Authorization).toBe(`Bearer ${token}`)
})
})
test("Auth request resets login when 401", async () => {
const loginReset = jest.fn()
try{
await authRequest.post(fakeUrl, {})
}
catch(error){
expect(loginReset).toHaveBeenCalledTimes(1)
}
})
})
testHookwithStore basically just creates a component wrapped around a provider. The last test is failing and I'm not sure how I would verify if the dispatch is actually working. Any clues here?
Apparently, there's a getActions() function on the mocked store.
test("Auth request resets login when 401", async () => {
try{
await authRequest.post(fakeUrl, {})
}
catch(error){
expect(store.getActions()[0].type).toBe("loginReset")
}
})