I am new to react and typescript.
I have a component Err.
I want to redirect it using react router 5 ?.
import React, { FC, Fragment, useEffect } from "react";
const Err: FC<{ error: string }> = ({ error }) => {
useEffect(()=>{
const time = setTimeout(() => {???(`/`)}, 2000);
return(()=> clearTimeout(time));
},[error])
return (
<Fragment>
<div>{error}</div>
<div>{"Contact site administrator"}</div>
</Fragment>
);
};
I don't know how to react router 5.
But if you use react router 4.
import React, { FC, Fragment, useEffect,useState } from "react";
import { Redirect } from "react-router";
const HandlerErr: FC<{ error: string }> = ({ error}) => {
const [booleURL, setBooleURL] = useState(false);
useEffect(()=>{
const time = setTimeout(() => {setBooleURL(true)}, 2000);
return(()=> clearTimeout(time));
},[error])
return (
<Fragment>
{ booleURL && <Redirect to="/"/>}
<div>{error}</div>
<div>{"Contact site administrator"}</div>
</Fragment>
);
};
or
import React, { FC, useEffect } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface OwnProps {
error: string;
}
type Props = OwnProps & RouteComponentProps<any>;
const HandlerErr: FC<Props> = ({ error, history }) => {
useEffect(() => {
const timeout = setTimeout(() => {
history.push(`/`);
}, 2000);
return () => {
clearTimeout(timeout);
};
}, [error]);
return (
<>
<div>{error}</div>
<div>Contact site administrator</div>
</>
);
};
export default withRouter(HandlerErr);
Related
Example code can be found below...
(Parent) App.tsx
import React, { useRef } from 'react';
import { Switch } from "react-router-dom";
import axios, { AxiosResponse } from 'axios';
import { AdminRoute } from './auth/protectedRoutes';
import Home from './views/Home';
export interface Data {
id: number;
name: string;
}
function App(): JSX.Element {
// variables
const searchDebouncerRef = useRef(false);
const [data, setData] = useRef<Array<Data>>([]);
// functions
async function updateData(searchString?: string | null) {
try {
const response: AxiosResponse<Array<Data>> = searchString
? await axios.get(`https://example.com/Api/Data$filter=contains(name, ${searchString})`)
: await axios.get('https://example.com/Api/Data');
if (searchDebouncerRef.current) {
return;
}
setData(response.data);
catch (e) {
console.log(e);
}
}
async function initData() {
try {
await updateData();
catch (e) {
console.log(e);
}
}
// setup
useEffect(() => {
initData();
}, []);
// render
return (
<>
<Switch>
<AdminRoute path="/">
<Home ref={searchDebouncerRef} updateData={updateData} data={data} />
</AdminRoute>
</Switch>
</>
)
}
export App;
(Child) Home.tsx
import React, { forwardRef } from 'react';
import { Data as DataRow } from '../App';
import Search from '../components/Search';
interface PROPS {
updateData: (searchString?: string | null) => void;
data: Array<DataRow>;
}
const Home: React.FC<any> = forwardRef(
({ updateData, data }: PROPS, ref) => {
return (
<div>
<Search isSearchDebouncingRef={ref} updateData={updateData} />
{data.map((row: DataRow) => ({
<p key={row.id}>{row.name}</p>
}))}
</div>
);
}
);
(Grandchild) Search.tsx
import React, { ChangeEvent, useCallback, useState } from 'react';
import { debounce } from 'lodash';
interface PROPS {
updateData: (searchString?: string | null) => void;
isSearchDebouncingRef: ???? // <-----------------------------------------------------
}
function Search({ updateData, isSearchDebouncingRef }: PROPS): JSX.Element {
// variables
const [searchText, setSearchText] = useState('');
const [searchDebouncerHasCompleted, setSearchDebouncerHasCompleted] = useState(false);
const searchDebouncer = useCallback(
debounce(() => {
setSearchDebouncerHasCompleted(true);
isSearchDebouncingRef.current = false;
}, 3000),
[]
);
// functions
function handleSearch(event: ChangeEvent<HTMLInputElement>) {
setSearchText(event.target.value);
isSearchDebouncingRef.current = true;
searchDebouncer();
}
// setup
useEffect(() => {
if (searchDebouncerHasCompleted) {
setSearchDebouncerHasCompleted(false);
updateData(searchText || null);
}
}, [searchDebouncerHasCompleted]);
// render
return <input type="text" value={searchText} onChange={(e) => handleSearch(e)} />; // ToDo: add icons(search, spinner, clear)
}
The grandchild file is where I am having trouble figuring out how to identify the type for the ref(see interface PROPS). Also the child file I would like to replace any, with the proper type if possible.
I think i'm misunderstanding some concept about jest functions, I'm trying to test if after a click my isCartOpen is being set to true; the function is working, being called with the desired value.
The problem is that my state isn't changing at all. I tried to set a spy to dispatch but i really can't understand how spy works or if it's even necessary in this case
// cart-icons.test.tsx
import { render, screen, fireEvent } from 'utils/test'
import CartIcon from './cart-icon.component'
import store from 'app/store'
import { setIsCartOpen } from 'features/cart/cart.slice'
const mockDispatchFn = jest.fn()
jest.mock('hooks/redux', () => ({
...jest.requireActual('hooks/redux'),
useAppDispatch: () => mockDispatchFn,
}))
describe('[Component] CartIcon', () => {
beforeEach(() => render(<CartIcon />))
it('Dispatch open/close cart action when clicked', async () => {
const { isCartOpen } = store.getState().cart
const iconContainer = screen.getByText(/shopping-bag.svg/i)
.parentElement as HTMLElement
expect(isCartOpen).toBe(false)
fireEvent.click(iconContainer)
expect(mockDispatchFn).toHaveBeenCalledWith(setIsCartOpen(true))
// THIS SHOULD BE WORKING, BUT STATE ISN'T CHANGING!
expect(isCartOpen).toBe(true)
})
})
// cart-icon.component.tsx
import { useAppDispatch, useAppSelector } from 'hooks/redux'
import { selectIsCartOpen, selectCartCount } from 'features/cart/cart.selector'
import { setIsCartOpen } from 'features/cart/cart.slice'
import { ShoppingIcon, CartIconContainer, ItemCount } from './cart-icon.styles'
const CartIcon = () => {
const dispatch = useAppDispatch()
const isCartOpen = useAppSelector(selectIsCartOpen)
const cartCount = useAppSelector(selectCartCount)
const toggleIsCartOpen = () => dispatch(setIsCartOpen(!isCartOpen))
return (
<CartIconContainer onClick={toggleIsCartOpen}>
<ShoppingIcon />
<ItemCount>{cartCount}</ItemCount>
</CartIconContainer>
)
}
export default CartIcon
// utils/test.tsx
import React, { FC, ReactElement } from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import { ApolloProvider } from '#apollo/client'
import { Elements } from '#stripe/react-stripe-js'
import { render, RenderOptions } from '#testing-library/react'
import store from 'app/store'
import { apolloClient, injectStore } from 'app/api'
import { stripePromise } from './stripe/stripe.utils'
injectStore(store)
const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Provider store={store}>
<ApolloProvider client={apolloClient}>
<BrowserRouter>
<Elements stripe={stripePromise}>{children}</Elements>
</BrowserRouter>
</ApolloProvider>
</Provider>
)
}
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) => render(ui, { wrapper: AllTheProviders, ...options })
export * from '#testing-library/react'
export { customRender as render }
I am totally a beginner in React and while practising I ran into this issue. Through searching, I found out that 'withRouter' is not supported anymore by 'react-router-dom v6'. But I can't figure out how to change my code compatibly to v6. Does anyone know how to change this code instead of using 'withRouter'? Thanks in advance!
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { readPost, unloadPost } from '../../modules/post';
import PostViewer from '../../components/post/PostViewer';
const PostViewerContainer = ({ match }) => {
// 처음 마운트될 때 포스트 읽기 API요청
const { postId } = match.params;
const dispatch = useDispatch();
const { post, error, loading } = useSelector(({ post, loading }) => ({
post: post.post,
error: post.error,
loading: loading['post/READ_POST']
}));
useEffect(() => {
dispatch(readPost(postId));
// 언마운트될 때 리덕스에서 포스트 데이터 없애기
return () => {
dispatch(unloadPost());
};
}, [dispatch, postId]);
return <PostViewer post={post} loading={loading} error={error} />;
};
export default withRouter(PostViewerContainer);
enter image description here
That is correct, the withRouter Higher Order Component (HOC) was removed in react-router-dom#6.
Since PostViewerContainer is a function component, just use the React hooks directly. There's no need really for the withRouter HOC. In this case it's the useParams hook you need to import and use.
Example:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom'; // <-- import useParams hook
import { readPost, unloadPost } from '../../modules/post';
import PostViewer from '../../components/post/PostViewer';
const PostViewerContainer = () => { // <-- remove match prop
// 처음 마운트될 때 포스트 읽기 API요청
const { postId } = useParams(); // <-- call hook and destructure param
const dispatch = useDispatch();
const { post, error, loading } = useSelector(({ post, loading }) => ({
post: post.post,
error: post.error,
loading: loading['post/READ_POST']
}));
useEffect(() => {
dispatch(readPost(postId));
// 언마운트될 때 리덕스에서 포스트 데이터 없애기
return () => {
dispatch(unloadPost());
};
}, [dispatch, postId]);
return <PostViewer post={post} loading={loading} error={error} />;
};
For reference, if you needed to still use an HOC for class based components you'd need to either convert them to function components or create a custom withRouter HOC.
Example:
import { useLocation, useNavigate, useParams } from 'react-router-dom';
const withRouter = Component => props => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return (
<Component
{...props}
location={location}
navigate={navigate}
params={params}
/>
);
};
export default withRouter;
Why is below code not working?
It returns false even when I set inverted to true;
it also logs hit so it does reach.
import React, { useEffect, useState } from 'react';
import { ComponentProps } from 'react';
import { useHistory } from 'react-router-dom';
type Props = {
} & ComponentProps<'div'>;
export function HeaderMaster({
...props
}: Props) {
const [inverted, setInverted] = useState(false);
const history = useHistory()
useEffect(() => {
setInverted(true); // this does work
history.listen((location) => {
console.log(location.pathname);
if (location.pathname === '/bestellen') {
setInverted(true); // this does not
console.log('hit');
}
else {
setInverted(false);
}
})
},[history]);
useEffect(() => {
console.log(inverted);
},[inverted])
return (
<>
</>
);
}
I wrote here is the code
import React, { FC, Fragment, useEffect } from "react";
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
const HandlerErr: FC<{ error: string }> = ({ error }) => {
useEffect(()=>{
const time = setTimeout(() => {history.push(`/`)}, 2000);
return(()=> clearTimeout(time));
},[error])
return (
<Fragment>
<div>{error}</div>
<div>{"Contact site administrator"}</div>
</Fragment>
);
};
I use the HandlerErr component to redirect. but for some reason it doesn't work history.push (/).I took a video
You need to use history form the react-router-dom
like
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Test extends Component {
render () {
const { history } = this.props
return (
<div>
<Button onClick={() => history.push('./path')}
</div>
)
}
}
export default withRouter(Test)
import React, { FC, useEffect } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
interface OwnProps {
error: string;
}
type Props = OwnProps & RouteComponentProps<any>;
const HandlerErr: FC<Props> = ({ error, history }) => {
useEffect(() => {
const timeout = setTimeout(() => {
history.push(`/`);
}, 2000);
return () => {
clearTimeout(timeout);
};
}, [error]);
return (
<>
<div>{error}</div>
<div>Contact site administrator</div>
</>
);
};
export default withRouter(HandlerErr);