I am new to React, I have JSON data with projects I want to dynamically link. I have created a component called "ProjectSingle" to display each one, however I can't seem to get the data to display.
const App = () => {
const [projects, setProjects] = useState([]);
const getProjects = () => {
fetch('http://localhost:8000/projects')
.then((res) =>{
return res.json();
})
.then((data) => {
setProjects(data);
})
}
useEffect(() => {
getProjects();
AOS.init({disable: 'mobile'});
},[]);
return (
<Router>
<div className="App">
<Switch>
{projects.map( project =>(
<Route exact path='/project/:slug' render={props => (
<ProjectSingle {...props} project={project}/>
)}/>
))}
</Switch>
<Footer/>
</div>
</Router>
);
}
export default App;
The requested JSON is below, I'm using "slug" to generate the slug.
{
"projects": [
{
"title": "Project One",
"image": "/img/wp-logo.png",
"slug": "project-one",
"key": 1
},
{
"title": "Project Two",
"image": "/img/wp-logo.png",
"slug": "project-two",
"key": 2
},
}
Although I'm not sure what data to put in my single component to get the data of the project iteration
const ProjectSingle = ({project}) => {
const { slug } = useParams();
return (
<>
<h1>{project.title}</h1>
</>
)
}
export default ProjectSingle;
Firstly, you only need one <Route path="/project/:slug" /> ... </Route> assigned. The :slug param can be used to dynamically determine what the content of this route will be.
<Route
exact
path="/project/:slug"
render={(props) => <ProjectSingle {...props} projects={projects} />}
/>
const ProjectSingle = ({projects}) => {
const { slug } = useParams();
const project = projects.find(p => p.slug === slug)
return (
<>
<h1>{project.title}</h1>
</>
)
}
export default ProjectSingle;
The route's child (ProjectSingle) will then useParams() to retrieve the value associated with :slug and use that to find the correct project from projects to render.
There may be an even more clever way of using the render() of the route and determining the desired slug & project at that stage - making the ProjectSingle a pure component without any knowledge of routing.
There are several important steps to achive this.
Include libraries like react-router, react-router-dom
Create a history
Render the Route with history
4.Define Navigation to the routes
Here is a sample as per your requirement:
Note: As per your design the routes will render in the same page. I have updated it to be in single file here. complete working example in codesandbox
import React, { useState } from "react";
import { Router, Switch, Route, useParams } from "react-router";
import { Link } from "react-router-dom";
import createHistory from "history/createBrowserHistory";
import "./styles.css";
const history = createHistory();
const Footer = () => {
return <div>Footer here.</div>;
};
const ProjectSingle = ({ project }) => {
const { slug } = useParams();
//TODO useEffect to get project details
return (
<>
<h1>Details of {slug} </h1>
<div>{JSON.stringify(project)}</div>
</>
);
};
export default function App() {
const [projects, setProjects] = useState([
{
title: "Project One",
image: "/img/wp-logo.png",
slug: "project-one",
key: 1
},
{
title: "Project Two",
image: "/img/wp-logo.png",
slug: "project-two",
key: 2
}
]);
/*const getProjects = () => {
fetch('http://localhost:8000/projects')
.then((res) =>{
return res.json();
})
.then((data) => {
setProjects(data);
})
}
useEffect(() => {
getProjects();
AOS.init({disable: 'mobile'});
},[]);
*/
return (
<Router history={history}>
<div className="App">
{projects.map((project) => {
return (
<div key={project.key}>
<Link to={`/project/${project.slug}`}> {project.title} </Link>
</div>
);
})}
<Switch>
{projects.map((project) => (
<Route
key={project.key}
exact
path="/project/:slug"
render={(props) => <ProjectSingle {...props} project={project} />}
/>
))}
</Switch>
<Footer />
</div>
</Router>
);
}
Related
When I click on the link in the HoverBooks Component to get to a new page where I can render the book location state in Book component, but when I press on it nothing happens. I think the error is in Route:
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/book:/book.Key">
<Book />
</Route>
<Route path="/signin">
<Signin />
</Route>
<Route path="/">
<Header />
<Home />
</Route>
</Switch>
</Router>
</div>
)
}
export default App
import React from 'react'
import { useLocation } from 'react-router-dom'
const Book = () => {
const {
state: { book },
} = useLocation()
console.log({ book })
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
export default Book
const HoverBooks = ({ ...book }) => {
const [inHoverBooks, setInHoverBooks] = React.useState(false)
return (
<>
<Link
to={{
pathName: `/book/${book.key}`,
state: {
book,
},
}}
>
<img
onMouseLeave={() => setInHoverBooks(false)}
onMouseEnter={() => setInHoverBooks(true)}
src={book.image}
key={book.key}
/>
</Link>
{inHoverBooks && (
<div className="hover__containter">
<h3>{book.bookName}</h3>
<h2>{book.by}</h2>
<h2>{book.Narreted}</h2>
<h2>{book.length}</h2>
<h2>{book.rating}</h2>
</div>
)}
</>
)
}
export default HoverBooks
Below is the correct form, e.g. /:someName, to define a route with URL params:
<Route path="/book/:bookKey">
<Book />
</Route>
And here is the right syntax to make a Link for the above route:
<Link
to={{
pathname: `/book/SOME_BOOK_KEY`, // replace SOME_BOOK_KEY with some value
state: {
book, // e.g. const book = { key: 'js', bookName: 'Learn JavaScript'}
},
}}
>
<img src="some_src" alt="something" />
</Link>
And you useParams and useLocation react-hooks to access the "URL params" and "location state" in a component:
const Book = () => {
const {
state: { book },
} = useLocation()
const { bookKey } = useParams();
console.log(book, bookKey)
// prints "book" object (from location state) and "bookKey" (from URL params)
return (
<div key={book.key}>
<h1>{book.bookName}</h1>
</div>
)
}
I would suggest you to add typescript to your ReactJS app. It helps you find errors early by doing "static Type-checking".
With react router you need to pass the component you want to render to the Route like this
const ComponentA = (props) => {...}
<Route path="/component-a" component={ComponentA} />
And here is how to link to component a
<Link to="/component-a" >Go to component A</Link>
I implement a project where i have to call API. From this API enter link description here first i show 250 countries and then create a details component where show all details individually by clicking a button or link. But i faced some problem. Since i have a little knowledge about react and API so i didn't understand how to call API for individually show country details in my details component. I need help because i want to know that how can I dynamically call single country from 250 countries API
----------This is the component here i load all countries by calling rest API. After that i couldn't call any single country
This is my country details component where i want to load individual country details
After getting some help i understand that api is coming in useState but i can not implement
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
const CountryDetails = () => {
const {countryName} = useParams();
const [country,setCountry] = useState([]); //use object
console.log(countryName);
console.log(country[0]);
useEffect(()=>{
// const url = `https://restcountries.eu/rest/v2/all`;
const url = `https://restcountries.eu/rest/v2/name/${countryName}`;
fetch(url)
.then(res => res.json())
.then(data => setCountry(data));
},[countryName])
// console.log(country);
return (
<div>
<h3>This is {countryName}</h3>
{/* <h2>{country[0]}</h2> */}
<h4>{countryName.capital}</h4>
</div>
); }; export default CountryDetails;
this is App.js in my project
import './App.css';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
useRouteMatch
} from "react-router-dom";
import Home from './Components/Home/Home';
import CountryDetails from './Components/CountryDetails/CountryDetails';
import NotFound from './Components/NotFound/NotFound';
function App() {
return (
<Router>
<Switch>
<Route path="/home">
<Home/>
</Route>
<Route path="/:countryName">
<CountryDetails></CountryDetails>
</Route>
<Route exact path="/">
<Home />
</Route>
<Route path="*">
<NotFound></NotFound>
</Route>
</Switch>
</Router>
);
} export default App;
I need to implement this component for showing individual country details
I think the issue is with how you attempt to render the details. The country data will still be in array format. countryName is the string route parameter, so it won't have any country specific properties to call.
<div>
<h3>This is {countryName}</h3>
{/* <h2>{country[0]}</h2> */}
<h4>{countryName.capital}</h4> // <-- countryName is string!!
</div>
You can simply map the country results similarly to how it was done on the main page. Destructure the detail values you want to use for rendering the details.
return country.map(({ capital, name }) => ( // <-- destructure all values needed
<div key={name} className="country">
<h3>
Country Name: {name}
</h3>
<div>
Capital: {capital}
</div>
<button type="button" onClick={history.goBack}>Back</button>
</div>
))
Full Demo Code:
Home page
const Home = () => {
const [countries, setCountries] = React.useState([]);
React.useEffect(() => {
fetch("https://restcountries.eu/rest/v2/all")
.then((res) => res.json())
.then((data) => setCountries(data));
}, []);
const history = useHistory();
return countries.map(({ capital, name }) => (
<div key={name} className="country">
<div>Country Name: {name}</div>
<div>Capital: {capital}</div>
<button
type="button"
onClick={() =>
history.push({
pathname: `/${name}`
})
}
>
Details
</button>
</div>
));
};
Details page
const CountryDetails = () => {
const { countryName } = useParams();
const [country, setCountry] = React.useState([]);
React.useEffect(() => {
fetch(`https://restcountries.eu/rest/v2/name/${countryName}`)
.then((res) => res.json())
.then((data) => setCountry(data));
}, [countryName]);
const history = useHistory();
return country.map(
({ capital, flag, name, nativeName, population, region, subregion }) => (
<div key={name} className="country">
<h3>Country Name: {name}</h3>
<img
src={flag}
alt="flag"
style={{
height: "100px"
}}
/>
<div>Capital: {capital}</div>
<div>Region: {region}</div>
<div>Subregion: {subregion}</div>
<div>Population: {population}</div>
<div>Native Name: {nativeName}</div>
<button type="button" onClick={history.goBack}>
Back
</button>
</div>
)
);
};
App
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Router>
<Switch>
<Route path="/:countryName">
<CountryDetails />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
</div>
);
}
You are making a mistake when accessing the data in CountryDetails component.
Instead of this
<h4>{countryName.capital}</h4>
Do this
<h4>{country[0].capital}</h4>
I'm trying out the new useRoutes hook from react-router-dom and it seems to be pretty interesting. The only problem, is that I can't figure out how I would pass props down to the components.
Before, I'd have a Route component, and I would select parts of local or global state there and pass it on, but how would I do it with useRoutes?
In the below sandbox, I'm trying to change the background based on the boolean value of isLoading. How would I pass isLoading on?
Edit
Here's the code:
import React from "react";
import { BrowserRouter as Router, Outlet, useRoutes } from "react-router-dom";
const Main = ({ isLoading }) => (
<div
style={{
height: "40vh",
width: "50vw",
backgroundColor: isLoading ? "red" : "pink"
}}
>
<Outlet />
</div>
);
const routes = [
{
path: "/",
element: <Main />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes);
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
);
};
export default function Entry() {
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
setInterval(() => {
setIsLoading(!isLoading);
}, 3000);
}, [isLoading]);
return (
<Router>
<App isLoading={isLoading} />
</Router>
);
}
Edit
I've considered passing in an isLoading argument to routes, but I feel like that won't be an efficient approach, because the whole tree will rerender at any route, regardless of whether or not it depends on isLoading or doesn't. Would a better approach be to use Switch and a custom Route component for routes that depend on isLoading and just use useSelector in that custom Route component?
I think this would work:
const routes = (props) => [
{
path: "/",
element: <Main {...props} />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes({isLoading}));
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
);
};
Instead of passing the array, you can create a function that returns a constructed array to the useRoutes hook:
const routes = (isLoading) => [
{
path: "/",
element: <Main isLoading={isLoading} />
}
];
code sandbox:
https://codesandbox.io/s/dark-dream-hx1y9
more pretty would be:
const routes = (props) => [
{
"/": () => <Main {...props} />
}
];
const App = ({ isLoading }) => {
const routing = useRoutes(routes(isLoading));
return (
<>
{routing}
{JSON.stringify(isLoading)}
</>
)
};
My Router is a simple component containing public and private routes. I have created an AuthRoute referring to the great tutorial from here
So, my Router looks like:
<Router>
<div>
<Navigation />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route path={ROUTES.SIGN_UP} component={SignUp} />
<Route path={ROUTES.SIGN_UP_SUCCESS} component={SignUpSuccess} />
<AuthenticationRoute path={ROUTES.HOME} component={Home} />
</div>
</Router>
and my AuthenticationRoute looks like this:
export const AuthenticationRoute = ({ component: Component, ...rest }) => {
const [authChecking, setAuthChecking] = useState(true);
const [{ isAuth }, dispatch] = useStateValue();
useEffect(() => {
checkLoggedIn().then(res => {
setAuthChecking(false);
dispatch({
op: 'auth',
type: 'toggleSessionAuth',
toggleSessionAuth: res
});
});
}, [])
if(authChecking)
return null;
if(!isAuth) {
return <Redirect to='/' />;
}
return <Route {...rest} render={(props) => (
<Component {...props} />
)
} />
}
Everything looks fine, however, my console returns such warning:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from the render. Or maybe you meant to call this function rather than return it.
I have tried different solutions using component/render etc, however, I could not find a solution to this problem and I have no idea what I am doing wrong.
For testing purposes, instead of rendering Component, I tried to render simple <div>test</div> and it worked fine. However, when I am passing a JSX component in props, it returns the warning shown above.
Implementation oh Home Component (Home.js):
export const Home = () => {
const [{ user }, dispatch] = useStateValue();
const { history } = useReactRouter();
const moveTo = path => {
dispatch({
op: 'other',
type: 'setView',
setView: path
});
history.push(path);
}
return (
<div className="pageMenuWrapper">
<h1 className="homeTitle">Hi {() => user ? `, ${user.username}` : ``}.</h1>
<div className="wrapper">
<Tile image={leagueico} alt="text" onClick={() => moveTo(ROUTES.TEST)}/>
<Tile comingSoon />
</div>
</div>
);
}
export default Home;
Could anyone help me solve this little problem?
The ButtonList returns a list of filtered Buttons that should each link to a new page. I would like to do it by adding dynamic routes, but I don't know how to write the code... How do I use history.push correctly? Any help would be highly appreciated.
export default function Home() {
const [searchValue, setSearchValue] = useState("");
const history = useHistory();
const filteredData = data.filter(item =>
item.name.toLowerCase().includes(searchValue.toLowerCase())
);
function handleSearch(value) {
setSearchValue(value);
}
const selectedItem = filteredData.find(item => item.name);
function handleSelect(item) {
history.push(`/home/${item.name}`);
}
return (
<WrapperDiv>
<StyledHeader />
<Switch>
<Route exact path="/home">
<Searchbar onSearch={handleSearch} />
<ButtonList data={filteredData} onClick={handleSelect} />
</Route>
<Route exact path="/home/{...}">
<StyledDiv>
<Button item={selectedItem} light />
<AccordionList />
</StyledDiv>
</Route>
</Switch>
</WrapperDiv>
);
}
export default function Button({ children, handleSelect }) {
return (
<button
to={{
pathname: "/home/" + data.name,
data: data
}}
onClick={handleSelect}
>
{children}
</button>
);
}
export const data = [
{
name: "Apple",
id: 1
},
{
name: "Banana",
id: 2
},
{
name: "Blueberry",
id: 3
}
];
one good way to achieve it would be to use React Router which makes creating dynamic routes really easy.
Writing the whole code is a little difficult here, therefore I found a good example of an easy way to implement react-router with your code.
Example
import { useHistory } from "react-router-dom";
function MyButton() {
let history = useHistory();
function triggerClick() {
history.push("/somewhere");
}
return (
<button type="button" onClick={triggerClick}>
Go somewhere
</button>
);
}