Redirect within a function (React) - html

I am trying to understand why this is returning an error Expected an assignment or function call and instead saw an expression #typescript-eslint/no-unused-expressions
as far as Im aware the value being passed into LessonId is correct.
Further upstream I am able to just <Redirect to="/edit/Lesson/"+LessonId; /> but im currently trying to redirect on doubleclick hence the function.
<tr className="lessonTableRow" key={lesson.lessonId} onDoubleClick={() =>{ lessonTableRowLink(lesson.lessonId)}}>
const lessonTableRowLink = (LessonId:number|undefined) => {
let link = '/edit/Lesson/'+LessonId;
<Redirect to= {link} />
}

You missed the return statement;
You should return it,so,react can render the page.
const lessonTableRowLink = (LessonId:number|undefined) => {
let link = '/edit/Lesson/'+LessonId;
return <Redirect to= {link} /> // add return
}
You should also check for NavLink or history#push

I'd recommend using react-router-dom's useHistory hook for this.
You're code could look something like this:
import React, { FC } from 'react';
import { useHistory } from 'react-router-dom';
const MyComponent: FC = () => {
const history = useHistory();
const lessonTableRowLink = (LessonId:number|undefined) => {
let link = '/edit/Lesson/'+LessonId;
history.push(link);
}
return (
...
<tr className="lessonTableRow" key={lesson.lessonId} onDoubleClick={() =>{ lessonTableRowLink(lesson.lessonId)}}>
...
);
};

JSX doesn't do anything on its own, and ESLint is warning you about this. Instead, you have to use React Router's history API
history.replace(link)

Related

react-router 6 Navigate to using params

In v5 i have such structure
{
path: '/someurl/:id',
exact: true,
render: ({ params }) => (<Redirect to={`/someurl/extraurl/${params.id}`} />),
}
How to refactor this to V6?
react-router-dom v6 no longer has route props, so you'll need to create a new component to gather the "props", or match.params in this case, and render the redirect as a Navigate component.
const MyRedirect = () => {
const { id } = useParams();
return <Navigate to={`/someurl/extraurl/${id}`} replace />;
};
...
{
path: '/someurl/:id',
element: <MyRedirect />,
}
...
<Route path={obj.path} element={obj.element} />
The accepted answer will work but I'll add my solution too, since it's a bit more dynamic. You can set up a function component that will make use of the useParams hook and the generatePath function so your intended destination gets the params from the initial route (whatever they may be):
import React, { FunctionComponent } from 'react';
import { generatePath, Navigate, useParams } from 'react-router-dom';
interface IProps {
to: string;
replace?: boolean;
state?: any;
}
const Redirect: FunctionComponent<IProps> = ({ to, replace, state }) => {
const params = useParams();
const redirectWithParams = generatePath(to, params);
return (
<Navigate to={redirectWithParams} replace={replace} state={state} />
);
};
export default Redirect;
Using this should work with your first example (and any other routes / redirects with dynamic params).

How do you loop through json with map and filter in React?

I have data in json format and I want to loop through it to render the same component (ContentThumbnail) eight times but with different titles and other content.
I have tried creating a function which accepts four parameters to achieve this. Here is the function I've written in a separate file called RenderContent.js:
import React from 'react';
import ContentThumbnail from './ContentThumbnail';
function RenderContentThumbnail(data, sectionName, wrapperStart, wrapperEnd) {
return (
<div>
{data
.filter(d => d.sectionName === { sectionName })
.map(filteredSection => (
{wrapperStart}
<ContentThumbnail {filteredSection.title} />
{wrapperEnd}
))}
</div>
);
}
export default RenderContentThumbnail;
And here is where I'm trying to execute that function in my component DefaultDashboard.js:
import React, { useEffect } from 'react';
import RenderContent from '../../content-thumbnail/RenderContent';
const DefaultDashboard = () => {
const { data } = useFetchData({ queryString: `${contentLibraryApiUrl}/GetContentForPage/Home` });
return (
RenderContentThumbnail(data, "topSection", "<div>", "</div>")
);
};
export default DefaultDashboard;
Is anyone able to help me see where I'm going wrong? I'm getting errors inside my map function and the page won't render at all.:(
Many thanks!
Katie
UPDATE!
I have made a tweak to the code to specify the prop, which is called "title", but I'm getting the following:
You should change the way you are rendering RenderContent:
const DefaultDashboard = () => {
const { data } = useFetchData({ queryString: `${contentLibraryApiUrl}/GetContentForPage/Home` });
return (
<RenderContent data={data} sectionName="topSection" wrapperStart="<div>" wrapperEnd= "</div>")
);
};
You can make it a lot easier, removing RenderContentThumbnail:
const DefaultDashboard = () => {
const { data } = useFetchData({ queryString: `${contentLibraryApiUrl}/GetContentForPage/Home` });
return (
{data
.filter(d => d.sectionName === "topSection")
.map(filteredSection => (<div>
<ContentThumbnail title={filteredSection.title} />
</div>))
}
);
};
export default DefaultDashboard;

Data not appearing after axios fetch (ReactJs)

Good day guys, i'm having a little trouble trying to req data from axios and showing it up at ReactJS, i'm able to console.log the data, but when i try to render it at the page, it simply dot not appear, could someone please lend me a hand? i will let the code and a print of the console.log, it's probably a newbie mistake since i'm new to ReactJs and JSON, but i would be very grateful if someone could explain!
CODE:
import Image from 'next/image'
import { useRouter } from 'next/router'
import React, { Component, useState } from 'react'
import ReactDOM from 'react-dom'
import { useHistory ,useLocation } from 'react-router-dom';
import axios from 'axios'
const options = {
method: 'GET',
headers: {
'Authorization': 'OAuth oauth_consumer_key="key", oauth_signature_method="PLAINTEXT", oauth_signature="2CC8D92526EE859C90AABB1F09F3B719&"'}
};
class Page extends React.Component {
state= {
motorData: [],
opa: []
};
componentDidMount() {
const make = "fiat"
axios.get(`https://api.trademe.co.nz/v1/Search/Motors/Used.json?make=${make}`, options)
.then(res => {
const cars = res.data;
console.log(cars)
this.state.motorData.push(cars.List[0].StartPrice)
console.log(this.state.motorData)
})
}
render() {
return <div>
Data:{this.state.motorData}
</div>
}
}
export default Page ```
Thank you guys so much in advance!
Instead of doing a push to your state, you need to use setState. Something like this:
componentDidMount() {
const make = "fiat"
axios.get(`https://api.trademe.co.nz/v1/Search/Motors/Used.json?make=${make}`, options)
.then(res => {
const cars = res.data;
this.setState(state => ({
motorData: [...state.motorData, cars.List[0].StartPrice]
});
})
}
Also, your motorData is an array, right? so in your render method you might want to loop in that array because you won't be able to print it I think. You should do something like this:
render() {
return (
<div>
Data:
// Adjust the return to be what you want it to render and
// don't forget to add a `key` property to the element you
// will return
{this.state.motorData.map(data => data)}
</div>
)
}
The problem is that, to set the state correctly, you should use setState, in this way:
this.setState((state) => ({ motorData: [...state.motorData, cars.List[0].StartPrice]}))
Using setState tells React that the state is changed and it have to re-render the component.

Strange behavior of useState() method of react hook while fetching data from axios

I am using axios library to fetch data from a json file through json-server.
When I am loading and using the response object in a single component it is working perfectly. But when I am passing this response object to child component from parent component it is not loading the data. Also not receiving any errors, can someone please help me to understand the difference and what is wrong with my approach?
//Scenario-1 : working perfectly fine:
import React, { useState, useEffect } from 'react';
import Display from './Display';
import Note from './note'
import axios from 'axios';
const App = () => {
const [notes, setNotes] = useState([])
const hook = () => {
axios.get('http://localhost:3001/notes')
.then(response => {
setNotes(response.data)
})
}
useEffect(hook, [])
return (
<div>
{notes.map(n => <Note key={n.id} note={n} />)}
</div>
)
}
export default App;
//Scenario-2 : Not working as expected, also no errors.
const Display = (props) => {
//Receiving data here, can see the values in console.
console.log('inside display, props.notex: ', props.notex);
const [notes, setNotes] = useState(props.notex);
//Blank object, why useState() method is not setting the value of "notes" from "props.notex".
console.log('inside display, notes: ', notes);
const generateRows = () => {
console.log('generateRows: ', notes)
return (
notes.map(n => <Note key={n.id} note={n} />)
)
}
return (
<div>
<ul>
{generateRows()}
</ul>
</div>
)
}
const App = () => {
const [notes, setNotes] = useState([])
const hook = () => {
axios.get('http://localhost:3001/notes')
.then(response => {
setNotes(response.data)
})
}
useEffect(hook, [])
return (
<div>
<Display notex={notes} />
</div>
)
}
export default App;
My guess is that useState is asynchronous, same as setState in Class components. Due to its async nature, you are not able to log anything - the log gets executed before the useState actually does anything.
If you really want to do it this way, you could initialize the value of the useState as an empty array and set up a useEffect hook, with the props.notex in your dependency array, something like this:
useEffect(() => {
if (props.notex) setNotes(props.notex)
}, [props.notex])
And then in the return
return (
<div>
<ul>
{notes.length && generateRows()}
</ul>
</div>
)
But you could just pass the props down from the parent to child without setting the state in the child component.
Hope this helps!

How to Access History Object Outside of a React Component

First of all, I am pretty familiar with the withRouter HoC, however, in this case, it doesn't help because I do not want to access the history object in a component.
I am trying to achieve a mechanism that will redirect the user to the login page if I receive back a 401 from a API endpoint. For making http requests I am using axios. I have around 60 endpoints that I need to cover, that are used in a dozen of components throughout my app.
I want to create a decorator function to the axios instance object, that:
1. makes the request
2. if fail && error_code = 401, update user route to `/login`
3. if success, return promise
The problem I have with the above is to update the route of the user. Previously, in react-router-v3, I could have imported the browserHistory object directly from the react-router package, which is no longer possible.
So, my question is, how can I access the history object outside of the React Component without passing it trough the call stack?
react-router v4 also provides a way to share history via the history package, namely createBrowserHistory() function.
The important part is to make sure that the same history object is shared across your app. To do that you can take advantage of the fact that node modules are singletons.
Create a file called history.js in your project, with the following content:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;
You can then just import it in your application via:
import history from "./history.js";
Please note that only Router accepts the history prop (BrowserRouter does not), so be sure to update your router JSX accordingly:
import { Router } from "react-router-dom";
import history from "./history.js";
// and then in your JSX:
return (
<Router history={history}>
{/* routes as usuall */}
</Router>
)
A working example can be found at https://codesandbox.io/s/owQ8Wrk3
Today, I faced the same issue. Maybe my solution helps somebody else.
src/axiosAuthenticated.js
import axios from 'axios';
import { createBrowserHistory } from 'history';
const UNAUTHORIZED = 401;
axios.interceptors.response.use(
response => response,
error => {
const {status} = error.response;
if (status === UNAUTHORIZED) {
createBrowserHistory().push('/');
window.location.reload();
}
return Promise.reject(error);
}
);
export default axios;
Also, if you want to intercept any request to add token stored in LocalStorage:
let user = JSON.parse(localStorage.getItem('user'));
var authToken = "";
if (user && user.token)
authToken = 'Bearer ' + user.token;
axios.defaults.headers.common = {'Authorization': `${authToken}`}
To use it, instead of importing from 'axios', import from 'axiosAuthenticated' like this:
import axios from 'utils/axiosAuthenticated'
Here is a solution that worked for me in latest version(5.2.0)
router/index.js
import { BrowserRouter, Switch } from "react-router-dom";
import { Routes } from "./routes";
export const Router = () => {
return (
<BrowserRouter>
<Switch>
<Routes />
</Switch>
</BrowserRouter>
);
};
router/routes.js
import React, { createRef } from "react";
import { Route, useHistory } from "react-router-dom";
import { PageOne, PageTwo, PageThree } from "../pages";
export const historyRef = createRef();
export const Routes = () => {
const history = useHistory();
historyRef.current = history;
return (
<>
<Route exact path="/" component={PageOne} />
<Route exact path="/route-one" component={PageTwo} />
<Route exact path="/route-two" component={PageThree} />
</>
);
};
And use it as below
historyRef.current.replace("/route-two");
I just encountered this same issue, and following is the solution I used to solve this problem.
I ended up creating a factory function which returns an object that has all my services functions. In order to call this factory function, an object with the following shape must be provided.
interface History {
push: (location: string) => void;
}
Here is a distilled version of my factory function.
const services = {};
function servicesFactory(history: History) {
const countries = countriesFactory(history);
const local = {
...countries,
};
Object.keys(local).forEach(key => {
services[key] = local[key];
});
}
Now the file where this function is defined exports 2 things.
1)This factory function
2)the services object.
This is what the countries service looks like.
function countriesFactory(h: History): CountriesService {
const countries: CountriesService = {
getCountries() {
return request<Countries>({
method: "get",
endpoint: "/api/countries",
}, h)
}
}
return countries;
}
And finally here is what my request function looks like.
function request<T>({ method, endpoint, body }: Request, history: History): Promise<Response<T>> {
const headers = {
"token": localStorage.getItem("someToken"),
};
const result: Response<T> = {
data: null,
error: null,
};
return axios({
url: endpoint,
method,
data: body,
headers,
}).then(res => {
result.data = res.data;
return result;
}).catch(e => {
if (e.response.status === 401) {
localStorage.clear();
history.push("/login");
return result;
} else {
result.error = e.response.data;
return result;
}
});
}
As you can see the request function exepcts to have the history object passed to it which it will get from the service, and the service will get it from the services factory.
Now the cool part is that I only ever have to call this factory function and pass the history object to it once in the entire app. After that I can simply import the services object and use any method on it without having to worry about passing the history object to it.
Here is the code of where I call the services factory function.
const App = (props: RouteComponentProps) => {
servicesFactory(props.history);
return (
// my app and routes
);
}
Hope someone else who finds this question will find this useful.
I am providing my solution here as accepted answer does not address the new versions of React Router and they require reload of the page to make that solution work.
I have used the same BrowserRouter. I have created a class with static functions and a member history instance.
/*history.js/
class History{
static historyInstance = null;
static push(page) {
History.historyInstance.push(page);
}
}
/*app-router.js/
const SetHistoryInstance = () => {
History.historyInstance = useHistory();
return (null);
};
const AppRouter = () => {
return (
<BrowserRouter>
<SetHistoryInstance></SetHistoryInstance>
<div>
<Switch>
<Route path={'/'} component={Home} />
<Route path={'/data'} component={Data} exact />
</Switch>
</div>
</BrowserRouter>
)};
Now you can import history.js anywhere in your app and use it.
One simple way is to useHistory() in App.js and then use render and pass history as an attribute of the component:
function App() {
const history = useHistory();
<Router>
<Route
path={nav.multiCategoriesNoTimer}
render={() => <MultiCategoriesNoTimer history={history} />}
/>
</Router>
}
const MixMultiGameNoTimer = (props: any) => {
if (true) {
return (
<NoQuestionsHereScreen history={props.history} />
);
}
}
const NoQuestionsHereScreen = (props: any) => {
return (
<div className='no-questions-here' >
<Button
title="Go back"
onClick={() => props.history.push(nav.home)}
/>
</div>
);
};
There is a bit of drilling, but it works and that for many future versions too>
I created a solution that could solve this issue.
Access react router dom history object outside React component
I think this approach will work with both React-router v4 and v5.