I have a function in a separate js file that checks status codes received from Api requests, and depending on the code this function needs to perform some actions:
function handleResponseCodes(res) {
try {
if (res.status ===200 ) {
return res.json();
} else if (res.status === 404) {
// here I need to redirect to /help
} else if (!res.ok) {
alert("Error")
} else {
if (res.ok) {
return res.data;
}
}
} catch (error) {
console.log(error);
}
}
and then this function is used like this with fetch requests.( project will have 100+ api requests so this way makes the process easy to follow).
fetch(url, obj)
.then((res) => handleResponseCodes(res)
If res.code === 404 I need to redirect the user to /help url, problem is that when I try to use useHistory() hook like this:
import {useHistory) from 'react-router-dom'
const history = useHistory()
//and in the function
else if (res.status === 404) {
// here I need to redirect to /help
history.push("/help")
I get error saying that useHistory hook must be used only in functional components. Is there a React way to redirect/push user to the /help from outside functional component?(basically from inside a function)
You can also use document.location.href = '/help'
You can just pass history as a parameter like this
...inside function component
const history = useHistory();
console.log(history)
//call function here
yourFunc(history)
Good question. We have so many api calls and have to handle those responses accordingly.
What I want to mention here is the close relation between routing and UI(for example, ProfilePage component).
According to Hook rules, we can't use hook function outside the React component, so we have to use the ways like #Zhang and #istar's answers.
But split the routing and component isn't a good practice. Of course some developers use routing config file for routing, but changing the url is usually being done in component.
I think routing is also one part of component.
So I want to recommend you that do the routing inside the component. Please take the result of response handling function and do the routing according to its result.
I had a same problem because of previous build functions.
import { useHistory } from 'react-router-dom';
function ReactComponent () {
const history = useHistory();
// some arrow function inside main react component
const someHandler = ( response, history ) => {
outsideFunction( response, history );
};
}
export default ReactComponent;
you can do as you like, but i am breaking it to two functions.
export function outsideFunction ( response, history ) {
if ( response.status === 201 ) {
checkedSubmitTypeHandler( location );
redirectPage(history);
return true;
}
}
export function redirectPage (history) {
return history.push(`/help`);
}
So, here is what happening that, you getting response wherever, Inside react main component or you handling in some other exported function. Passing the parameter through the arrow functions used inside react component.
It's not best practice to do it like this.
You should create a useHistoryHook which will be resuable in your application and you can call it like this.
// history.js page inside src
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
// useHistorypush component
import React from 'react';
import history from '../../history';
const useHistoryPush = (pathname,onLoadWait = 30) => {
const refContainer = React.useRef(false);
setTimeout(() => {
refContainer.current = true;
}, onLoadWait);
return (link) => {
if (refContainer.current) {
history.push(link);
}
};
};
export default useHistoryPush;
// implementation inside page
import useHistoryPush from '../use-history-push';
const Reactcomponent = () => {
const push = useHistoryPush();
React.useEffect(() => {
if (successResponse) {
// on some condition you can call it inside useeffect and it will redirect to the url mention inside *push
push(`/help`);
}
}, [successResponse, push]);
}
export default Reactcomponent;
Related
Cheers,
I have the bellow React code, where i need to send an HTTP Request to check if my actual user have permission or not to access my page.
For that, im using useEffect hook to check his permission every page entry.
But my actual code does not wait for authorize() conclusion. Leading for /Unauthorized page every request.
What i am doing wrong?
import React, { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";
export default function MypPage() {
const navigate = useNavigate();
const [authorized, setAuthorized] = useState(false);
const authorize = useCallback(async () => {
// it will return true/false depending user authorization
const response = await security.authorize("AAA", "BBB");
setAuthorized(response);
});
useEffect(() => {
authorize();
if (authorized) return;
else return navigate("/Unauthorized");
}, [authorize]);
return <div>MypPage</div>;
}
Thanks.
You are experiencing two issues:
Expecting a non-awaited asynchronous function to be awaited.
Expecting setState function to be synchronous.
Issue 1
authorize is asynchronous and is called within the useEffect but is not awaited. This means that the following code will be executed immediately before authorize has been completed.
Solution
A useEffect cannot be passed an asynchronous function, but a new asynchronous function can be created and then called from within the useEffect. This would allow you to create a proper asynchronous flow that works as you are expecting.
Example:
useEffect(() => {
// create new async function to be run.
const myNewAsyncMethod = async () => {
// async request.
const response = await request();
// logic after async request.
if (response) {
return;
}
}
// trigger new async function.
myNewAsyncMethod();
}, [])
Issue 2
setState functions are not synchronous, they do not update the state value immediately. They ensure that on the next render of the component that the value will be updated. This means that accessing authorized directly after calling setAuthorized will not result in the value you are expecting.
Solution
Just use the value you passed to the setState function.
Example:
// get new value (i.e. async request, some calculation)
const newValue = true;
// set value in state.
setAuthorized(newValue);
// do further logic on new value before next render.
if (newValue) {
// do something
}
Conclusion
Altogether you should end up with something similar too:
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";
export default function MypPage() {
const navigate = useNavigate();
const [authorized, setAuthorized] = useState(false);
useEffect(() => {
// don't need to memoize the authorize function using `useCallback`
// if you are only going to use it in a `useEffect`
const authorize = async () => {
// run async request.
const response = await security.authorize("AAA", "BBB");
// setAuthorized will not update authorized immediately.
// setAuthorized will ensure that on the next render the new
// value is available.
setAuthorized(response);
// access value from response of async request as authorized
// will not be updated yet.
if (!response) {
navigate("/Unauthorized");
}
};
// call the async function.
authorize();
}, [navigate, setAuthorized]);
return <div>MyPage</div>;
}
useEffect hooks do not wait for async code. Since you want to check every time you access this page, you really don't need to set a state for this.
import React, { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";
export default function MypPage() {
const navigate = useNavigate();
const authorize = useCallback(async () => {
// it will return true/false depending user authorization
const isAuthorized = await security.authorize("AAA", "BBB");
// If the user is authorized this function will finish
// If not, the user will be redirected.
if(isAuthorized) return;
navigate("/Unauthorized");
});
// With will only be called when the component is mounted
useEffect(() => {
authorize();
}, []);
return <div>MypPage</div>;
}
Whenever I needed to call async-wait function in useEffect, I always did like following.
I hope this would be helpful for you.
import React, { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";
export default function MypPage() {
const navigate = useNavigate();
useEffect(() => {
(async () => {
const authorized= await security.authorize("AAA", "BBB");
if (authorized) return;
else return navigate("/Unauthorized");
})()
}, []);
return <div>MypPage</div>;
}
Additionally to the other answers this functioality should be hanfled on the back end. You can modify js code on the fly from the browser, the authorization functionality is better handled with file permissions on the server side.
I am following along in a React course on Udemy. In this module, we have a simple task app to demonstrate custom hooks. I've come across a situation where the "task" state is being managed in the App.js file, the "useHttp" custom hook has a function "fetchTasks" which accepts "transformTasks" as a parameter when called inside App.js. The issue I am having is that "tranformTasks" manipulates the "tasks" state inside App.js, but it is actually being called and executed inside the "useHttp" custom hook. Would really love some help understanding the mechanism for how this works. How can the state be manipulated while called from another file without the state being passed in? The code does work as intended. Here's the github link to the full app, and below are the two relevant files: https://github.com/yanichik/react-course/tree/main/full-course/custom-hooks-v2
Here is the App.js file:
import React, { useEffect, useMemo, useState } from "react";
import Tasks from "./components/Tasks/Tasks";
import NewTask from "./components/NewTask/NewTask";
import useHttp from "./custom-hooks/useHttp";
function App() {
// manage tasks state here at top level
const [tasks, setTasks] = useState([]);
const myUrl = useMemo(() => {
return {
url: "https://react-http-104c4-default-rtdb.firebaseio.com/tasks.json",
};
}, []);
const { isLoading, error, sendRequest: fetchTasks } = useHttp();
useEffect(() => {
// func transforms loaded data to add id (firebase-generated), push to loadedTasks, then
// push to tasks state
const transformTasks = (taskObj) => {
let loadedTasks = [];
for (const taskKey in taskObj) {
loadedTasks.push({ id: taskKey, text: taskObj[taskKey].text });
}
setTasks(loadedTasks);
};
fetchTasks(myUrl, transformTasks);
// if you add fetchTasks as a dependency this will trigger a re-render each time states
// are set inside sendRequest (ie fetchTasks) and with each render the custom hook (useHttp)
// will be recalled to continue the cycle. to avoid this, wrap sendRequest with useCallback
}, [fetchTasks, myUrl]);
const addTaskHandler = (task) => {
setTasks((prevTasks) => prevTasks.concat(task));
};
return (
<React.Fragment>
<NewTask onEnterTask={addTaskHandler} />
<Tasks
items={tasks}
loading={isLoading}
error={error}
onFetch={fetchTasks}
/>
</React.Fragment>
);
}
export default App;
And here is the "useHttp" custom hook:
import { useState, useCallback } from "react";
// NOTE that useCallback CANNOT be used on the top level function
function useHttp() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const sendRequest = useCallback(async (httpConfig, applyFunction) => {
setIsLoading(true);
setError(false);
try {
const response = await fetch(httpConfig.url, {
method: httpConfig.method ? httpConfig.method : "GET",
headers: httpConfig.headers ? httpConfig.headers : {},
body: httpConfig.body ? JSON.stringify(httpConfig.body) : null,
});
// console.log("response: " + response.method);
if (!response.ok) {
throw new Error("Request failed!");
}
const data = await response.json();
applyFunction(data);
// console.log("the formatted task is:" + applyFunction(data));
} catch (err) {
setError(err.message || "Something went wrong!");
}
setIsLoading(false);
}, []);
return { sendRequest, isLoading, error };
}
export default useHttp;
Sounds like you're learning from a decent course. The hook is using a technique called "composition". It knows you'll want to do some processing on the data once it has been fetched and let's you pass in (the applyFunction variable) your own snippet of code to do that processing.
Your snippet of code is just a function, but all parties agree on what parameters the function takes. (This is where using typescript helps catch errors.)
So you pass in a function that you write, and your function takes 1 parameter, which you expect will be the data that's downloaded.
The useHttp hook remembers your function and once it has downloaded the data, it calls your function passing in the data.
If you've used some of your own variables within the function you pass to the hook, they get frozen in time ... sort-of. This can of worms is a topic called 'closures' and I'm sure it will come up in the course if it hasn't already.
Im migrating over from Angular5. In angular5 i was using services, now im trying to use react.
Im trying to call a function from globalService.js, and do something with it in app.js.
so far i got this error:
TypeError: _globalService__WEBPACK_IMPORTED_MODULE_5__.default.PerformRequest is not a function
test data:
app contains:
import doSomething from "./globalService";
console.log("doing a request",doSomething.PerformRequest('test'));
globalservice:
const PerformRequest = function(data) {
console.log("data is:",data);
}
export default PerformRequest;
test data end
Ideally im trying to translate my angular code to react code.
here is my angular old code:
app call:
click() {
this.GlobalService.PerformRequest('/crime').pipe(takeUntil(this._destroyed$)).subscribe((data:any) => {
DO SOMETHING WITH DATA HERE
});
}
globalservice call:
PerformRequest(params) {
console.log(this.baseURL + params);
return this.http.get(this.baseURL + params,this.options)
.pipe(
//catchError(this.handleError)
);
}
Q1: What am i doing wrong in my first example above?
Q2: How would i rewrite my angular data to react language so i can use it to call from a component the same was as im calling it?
Q1: Your import is not correct, it should be
import PerformRequest from './globalService';
if you keep the globalService the way it's currently return. If you do want something like a service you could export an object containing all the methods.
export default {
PerformRequest,
};
Q2:
I'd suggest you look into hooks to reproduce what you already have as a service.
You could do something like that:
import { useEffect, useState } from 'react';
// define a hook
const useFetchUrl = (url) => {
// useState are used to store some state
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState();
// useEffect is done for side effect
useEffect(() => {
fetch(url)
.then(resp => resp.json())
.then(data => setData(data);
}, [url];
return {
isLoading,
data,
};
}
const Component = () => {
const { loading, data } = useFetchUrl('/some/url');
if (!loading) {
return <div>Loading...</div>;
}
return <pre>{JSON.stringify(data)}</pre>;
};
swr is a nice library that provides you with hooks you could use to do the same kind of thing without having to write your own custom hooks and dealing with some more complicated cases.
My folder structure:
|--App
|--Components
|--PageA.js
|--PageB.js
|--PageC.js
|--common-effects
|--useFetching.js
I am refactoring my code to fetch data from API, using react hooks.
I want to dispatch an action from useEffect in useFetching.js that is intercepted by saga middleware. The action should be dispatched only when the components(PageA, PageB, PageC) mount.
I am using redux, react-redux and redux-saga.
PageA.js:
function(props) {
useFetching(actionParams)
//....//
}
Similar code for PageB and PageC components.
I have abstracted the reusable code to fetch data in useFetching Custom hook.
useFetching.js
const useFetching = actionArgs => {
useEffect( () => {
store.dispatch(action(actionArgs)); // does not work
})
}
I don't know how to access redux dispatch in useFetching. I tried it with useReducer effect, but the sagas missed the action.
Version using react-redux hooks:
You can even cut out the connect function completely by using useDispatch from react-redux:
export default function MyComponent() {
useFetching(fetchSomething);
return <div>Doing some fetching!</div>
}
with your custom hook
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
Edit: removed dispatch from custom hook as suggested by #yonga-springfield
Note: React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.
You would need to pass either bound action creators or a reference to dispatch to your hook. These would come from a connected component, same as you would normally use React-Redux:
function MyComponent(props) {
useFetching(props.fetchSomething);
return <div>Doing some fetching!</div>
}
const mapDispatch = {
fetchSomething
};
export default connect(null, mapDispatch)(MyComponent);
The hook should then call the bound action creator in the effect, which will dispatch the action accordingly.
Also, note that your current hook will re-run the effect every time the component is re-rendered, rather than just the first time. You'd need to modify the hook like this:
const useFetching = someFetchActionCreator => {
useEffect( () => {
someFetchActionCreator();
}, [])
}
This is just to bring some optimization to #Alex Hans' answer.
As per the documentation here. A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
With this in mind, we need not send a reference to the dispatch function to the useFetching hook as a parameter but rather, simply not send it and rather simply use it from within the useFetching hook with the appropriate imports.
Here's an excerpt of what I mean.
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
I can't ascertain this example will fit without errors in your codebase in your case but just trying to explain the idea/concept behind this post.
Hope this helps any future comer.
Alex Hans right decision with dispatch, but to eliminate request loops to api you can specify the dependence on dispatch ( I used Redux Toolkit )
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import axios from 'axios'
import { getItemsStart, getItemsSuccess, getItemsFailure } from '../features/itemsSlice'
const fetchItems = () => async dispatch => {
try {
dispatch(getItemsStart());
const { data } = await axios.get('url/api')
dispatch(getItemsSuccess(data))
} catch (error) {
dispatch(getItemsFailure(error))
}
}
const PageA = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.dataSlice)
useEffect(() => {
dispatch(fetchItems())
}, [dispatch])
return (
<ul>
{items.map(item => <li>{item.name}</li>}
</ul>
)
}
export default PageA
it is important to passed dependency parameter of dispatch in the useEffect(() => {...}, [dispatch])
useEffect(() => {
fetchData();
}, []);
async function fetchData() {
try {
await Auth.currentSession();
userHasAuthenticated(true);
} catch (e) {
if (e !== "No current user") {
alert(e);
}
}
dispatch(authentication({ type: "SET_AUTHING", payload: false }));
}
I need to write a test with the following steps:
get user data on mount
get project details if it has selectedProject and clientId when they change
get pages details if it has selectedProject, clientId, and selectedPages when they change
render Content inside Switch
if doesn't have clientId, Content should return null
if doesn't have selectedProject, Content should return Projects
if doesn't have selectedPages, Content should return Pages
else Content should render Artboard
And the component looks like this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUserData } from "../../firebase/user";
import { selectProject } from "../../actions/projects";
import { getItem } from "../../tools/localStorage";
import { getProjectDetails } from "../../firebase/projects";
import { selectPages } from "../../actions/pages";
import Pages from "../Pages";
import Projects from "../Projects";
import Artboard from "../Artboard";
import Switch from "../Transitions/Switch";
import { getUserId, getClientId } from "../../selectors/user";
import { getSelectedProject } from "../../selectors/projects";
import { getSelectedPages, getPagesWithDetails } from "../../selectors/pages";
import { getPagesDetails } from "../../firebase/pages";
const cachedProject = JSON.parse(getItem("selectedProject"));
const cachedPages = JSON.parse(getItem("selectedPages"));
const Dashboard = () => {
const dispatch = useDispatch();
const userId = useSelector(getUserId);
const clientId = useSelector(getClientId);
const selectedProject = useSelector(getSelectedProject) || cachedProject;
const selectedPages = useSelector(getSelectedPages) || cachedPages;
const pagesWithDetails = useSelector(getPagesWithDetails);
useEffect(() => {
dispatch(
getUserData(userId)
);
cachedProject && selectProject(cachedProject);
cachedPages && selectPages(cachedPages);
}, []);
useEffect(() => {
if (selectedProject && clientId) {
dispatch(
getProjectDetails(
clientId,
selectedProject
)
);
}
}, [selectedProject, clientId]);
useEffect(() => {
if (selectedPages && selectedProject && clientId) {
const pagesWithoutDetails = selectedPages.filter(pageId => (
!Object.keys(pagesWithDetails).includes(pageId)
));
dispatch(
getPagesDetails(
selectedProject,
pagesWithoutDetails
)
);
}
}, [selectedPages, selectedProject, clientId]);
const Content = () => {
if (!clientId) return null;
if (!selectedProject) {
return <Projects key="projects" />;
}
if (!selectedPages) {
return <Pages key="pages" />;
}
return <Artboard key="artboard" />;
};
console.log("Update Dashboard")
return (
<Switch>
{Content()}
</Switch>
);
};
Where I use some functions to fetch data from firebase, some to dispatch actions, and some conditionals.
I'm trying to get deep into testing with Jest and Enzyme. When I was searching for testing approaches, testing useEffect, variables, and conditions, I haven't found anything. All I saw is testing if a text changes, if a button has get clicked, etc. but what about testing components which aren't really changing anything in the DOM, just loading data, and depending on that data, renders a component?
What's the question here? What have you tried? To me it seems pretty straightforward to test:
Use Enzymes mount or shallow to render the component and assign that to a variable and wrap it in a store provider so it has access to a redux store.
Use jest.mock to mock things you don't want to actually want to happen (like the dispatching of actions) or use something like redux-mock-store.
Use that component ".find" to get the actual button you want.
Assert that, given a specific redux state, it renders correctly.
Assert that actions are dispatched with the proper type and payload at the proper times.
You may need to call component.update() to force it to rerender within the enzyme test.
Let me know if you have more specific issues.
Good luck!