After checking out this react-router tutorial, I tried to integrate what I learned here into my project.
My scenario is similar to the number 2 from the tutorial, except that when the user enters /, I want to fetch an api and redirect to the first category comming from the api that looks like this [{'category':'electronics', 'items':[{..}],..},..]
my router looks like this
import RoutaZ from 'Routes.js';
...
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRedirect to={RoutaZ.state.data[0].name} />
<Route path=":category" components={Container, SideNavigation} />
</Route>
my Routes.js looks like this
let Routes = React.createClass({
getInitialState () {
return {
data: null
}
},
componentDidMount() {
var self = this;
fetchData().then(function(results){
self.setState({data: results.data});
})
},
render() {
/* want to return the the first category from api */
return this.state.data[0].name
}
});
In the router, RoutaZ.state.data[0].name returns undefined because the initial state is null. If I set the initial state to [{'category':'hello',...}], it returns hello and redirects to the hello page as expected. What can I do to redirect after the db is fetched?
1) How can I use onEnter from react-router with my current config?
2) How and where can I set a parent component to my router handle all the fetching and pass it to the router as a child?
EDIT: This is just a little part of my application,which is finished but I only have this redirect issue. I know that I can use redux-saga but all my application is already done and would have to redo it completely which I cannot afford.
1- I tried using onEnter but don't know where I should place it.
2-Somewhere in my application is fetched data from the parent component and gave it as props to the child and the child received the data from the api.
Solved it. I had to push the results when the component mounted
componentDidMount() {
var self = this;
fetchData().then(function(results){
router.push(results.data[0].category);
})
},
Related
i have a gallery of images in the main view, and I want to be able to click each image to open a new child view showing details (eg text such as image title). my hardcoded version -- where images and text are stored in frontend -- works. but when I store the data in a MySQL database, i'm running into a problem -- eg unable to drill down into the details when an image is clicked.
the data seems to be fetched properly from backend to frontend because the images of the gallery are showing up. in my mind, the difference (between the hardcoded version and database-connected version) lies largely in react router v4's setup, perhaps a syntax issue that is preventing the paths from matching or preventing data from being passed to the child view:
ReactDOM.render(
..
<Route path="/articles/:PostId" render={(props) => (<Post articles={this.state.blurbs} {...props} />)} />
when it comes to errors, I get different messages depending on the browser:
one says
"TypeError: Unable to get property 'blurbs' of undefined or null reference;"
other says
"TypeError: this.state is undefined"
for brevity, my array state is initialized as such:
class App extends React.Component
..
this.state = {
blurbs: []
}
the value of the array state is updated as such:
componentDidMount()
..
let self = this;
..
return response.json();
...
.then(function(data) {
self.setState({blurbs: data});
})
in child component, data is rendered based on unique id in database as such:
render
..
return
..
{this.props.articles[this.props.match.params.PostId].Title}
so going back, what's wrong with the syntax in react router assuming everything above makes sense? tia
The issue is that the data coming from the API (eg: MySQL) is likely an array. However, you're treating it like a js object.
In your child component's render function, you need to use the findIndex method to find which element of the array that contains the value of your PostId URL parameter (docs). This is assume your table has an id element that is your primary key.
render() {
const idx = this.props.articles.findIndex(x => x.id === this.props.match.params.PostId);
const article = idx > -1 ? this.props.articles[idx] : {};
return (
...
{article.Title}
...
);
to bring "closure" to this thread, i'm sharing what worked for me. instead of passing all props from parent to child via react router, i ended up passing some props from parent to child through the image link.
the issue -- that props was not completely getting from parent to child -- was not immediately clear to me. i narrowed down my issue by tinkering around (eg substituting parts of the project with hardcode to identify the problematic parts of the code).
i found a thread where someone else was experiencing similar blockage in data transfer via react router. after several roadblocks, here are the changes that got me moving forward again.
in parent component, state reference was removed from react router to look as such:
ReactDOM.render(
..
<Route path="/articles/:PostId" render={(props) => (<Post {...props}/>)} />
in parent component, state reference was added to image link to use Link as a data transfer medium as such:
render() {
return (
<div>
{this.state.blurbs.map(image => (
<Link key={image.PostId}
to={{pathname: `/articles/${image.PostId}`,
state: { blurbs: this.state.blurbs }
}}>
<img src={image.src}/>
</Link>
))
}
</div>
in child component, this.props.location.state.blurbs was added as replacement to make the data (originally fetched in parent) accessible to child as such:
render
..
return
..
{this.props.location.state.blurbs[this.props.match.params.PostId].Title}
here's the link to the other thread:
How do i pass state through React_router?
I'm working on my first complicated React app and I am making a request to a movie API. My site allows the user to do a search in a searchbar for whatever movie, show, actor, etc... that they are searching for. I'm pulling the user's search query and inserting it into an api request like this:
export const getDetails = (id) => {
return new Promise(function(resolve, reject) {
axios.get(`https://api.themoviedb.org/3/movie/` + id +`?api_key=&language=en-US`)
.then(function(response) {
resolve(response)
})
.catch(function(error) {
reject(error)
})
})
}
I'm able to get the data like this and console.log it:
import React, { Component } from 'react';
import Header from '../header';
import {Link} from 'react-router-dom';
import axios from 'axios';
import Footer from '../Footer.js';
import Searchbar from '../header/searchbar.js';
import List from '../results/list';
import {getDetails} from '../api/getDetails';
class Detail extends Component {
constructor(props) {
super(props);
this.state = {
id: this.props.match.params.id,
result: null,
error: false,
}
}
componentWillMount() {
getDetails(this.state.id).then(function(response){
this.setState({result: response});
console.log(response.data.original_title);
console.log(response.data.homepage);
console.log(response.data.popularity);
console.log(response.data.release_data);
console.log(response.data.overview);
}.bind(this)).catch(function(err) {
this.setState({
result:"There was a problem loading the results. Please try again.",
error: true
})
}.bind(this))
}
render() {
return(
<div>
<Header/>
<div className="details-container">
<h2>Details: </h2>
</div>
</div>
)
}
}
export default Detail
Console.logging it in the componentWillMount function successfully logs the data but I am not able to access the data in the render function via something like {response.data.orginal_title). How would I render the data being logged in componentWillMount?
TLDR; You can access your state variables from within your render function via this.state. Something like: console.log(this.state.result.data.origin_title) outside of the jsx and {this.state.response.data.orginal_title} inside the jsx.
P.S. You are using the correct this.
The following are picky recommendations and explanations, feel free to disregard.
It's recommended to make requests for data in componentDidMount. That can be read here in the docs for componentDidMount.
You're using arrow functions already in your get details function, if you convert the rest of your functions to arrow functions you no longer have to explicitly bind this to each one; it's automatically set be the this of it's parent. See the "No Separate This" section in the MDN docs
If you don't need any of the header information I would save response.data into your state so you don't have to type as much when you want to access the data. this.state.result.original_title vs this.state.result.data.original_title. That's just me and I'm lazy.
axios does return a promise like Eric said so you don't actually need to wrap it in the extra promise. You can just straight up return it and since arrow functions automatically return one line expressions you can spiff that up into a one liner:
export const getDetails = id => axios.get(`https://api.themoviedb.org/3/movie/${id}?api_key=&language=en-US`)
Finally you should be able to access the data you've stored in your state from your render function as mentioned in #3 above. Outside of the JSX you can console.log it like normal console.log(this.state.result), inside your JSX, however, you will need to make sure you escape with {} like: <div>{this.result.original_title}</div>
Small working example here: https://codesandbox.io/s/zqz6vpmrw3
You can simply use
{this.state.result}
inside the render.
My use case is to have a universal page view statistic function for react-router v4, so it may looks like:
<Provider store={store}>
<Router>
<Tracker>
<App />
</Tracker>
</Router>
</Provider>
My advanced requirement is to get all params from route, so a URL of /users/kitty/books/199231 can be parsed to:
{
path: '/users/:username/books/:isbn',
params: {
username: 'kitty',
isbn: '199231'
}
}
The problem is, my Tracker component can never get access to a nested route's path and match prop, even if I use withRouter with my Tracker component, it gets a path of /
I know in theory my requirement is not correct because we can put two or more <Router> side by side by side, so my real case would be "get the deepest route's params"
Is it possible to archive this? or is there any solution that my page view statistics can parse a URL to it's corresponding route path and params?
Using the newer version of React Router, how do you pass parameters to the route you are transitioning to if you are transitioning using the browserHistory.push()? I am open to using some other way to transitioning if browserHistory does not allow this. I see that in my top most child component in React Router there is this.props.routeParams but I can't seem to get this populated with values I want from the previous route when transitioning.
Now you will have do something like this
history.push({
pathname: '/about',
search: '?the=search',
state: { some: 'state' }
})
Here is the link for API documentation
for react router v4 we can do things this way:
const history = useHistory();
history.push({
pathname: "/create-opinion/"+user,
state:{ username:userName}
};
and to get it from the other component simply:
const history = useHistory();
then to get username:
const username = history.location.state.username;
don't forget to import useHistory from react-router-dom
if you haven't install react-router-dom
yet type:
$ npm install --save react-router-dom
If you want to pass a path parameter you can do it as follows. I am using sample component Merchant.
Define Route
<Route exact path="/merchant/:id" render={(props) => <Merchant id={props.match.params.id}/>} />
Define History
const history = createBrowserHistory({
basename:''
})
Add to history with parameter
history.push(`/merchant/${id}`)
I have a legacy web app that is being replaced with React and it is called by these various systems by being passed a URL (derived from records in and existing DB system) The app renders folder views, of files in a sandboxed container according to the path passed in.
I need to be able to identify these requests and route them to a File/Folder handing page, treating everything after the '/files/ part of the path down as a parameter to the path of the file or folder.
<Route path="/" handler={Master}>
<Route name="files_link" path="files" handler={Files} />
<Route name="filesWithPath_link" path="files/*:path" handler={Files} />
</Route>
I would like to be able to handle requests passed into
/files
and have it handled in the page (defaulting to top level folder because no path parameter passed.
and have all the following examples of possible URL just passed to the router and extract the path parameter from the bit after /files/.
/files/folder path=folder
/files/folder/filename.ext path=folder/filename.ext
/files/folder/folder1 path=folder/folder1
/files/folder/folder1/filename.ext path=folder/folder1/filename.ext
/files/folder/folder1/folder2/ path=folder/folder1/folder2
/files/folder/folder1/folder2/filename.ext path=folder/folder1/folder2/filename.ext
.... and so on ...
When I try this I get an error
Uncaught Invariant Violation: Missing "splat" parameter for path "/beta/page/files/*:path"
I'm currently using react 0.14.2 and react-router 0.13.4.
How would you go about handling a variable length path in this manner in React-router?
You need to use it like so:
<Route name="filesWithPath_link" path="files/*" handler={Files} />
And then in your React component you can access the value of the splat:
// The '*' in the path match gets assigned to this.props.params.splat
var path = this.props.params.splat;
For completeness, the splat parameter can be accessed from the useParams() hook as property 0:
const splat = useParams()[0];
In React Router v6, the "*" can be accessed from useParams() as a property named *.
const wildcard = useParams()["*"];
And of course you can always get the entire URL (i.e. everything including the bit before the *) using useLocation()
Example in TypeScript with a named parameter:
<Route path="/files/:path">
<Files/>
</Route>
import { useParams } from 'react-router';
export function Files(): React.ReactElement {
const splat = useParams<{ path: string }>().path;
}