I have a problem with passing context to route. I get an error when i click a link that goes to my component where context was passed from App component. Below is that component with App (only one import just to show where Context is coming from):
App.js
import { Context } from './Context';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
cryptolist: []
}
}
componentDidMount = () => {
fetch('https://api.coinmarketcap.com/v2/ticker/?structure=array')
.then(response => response.json())
.then(json => this.setState({
cryptolist: json.data
}))
}
render() {
return (
<div>
<Menu />
<Context.Provider value={this.state}>
<Userlist />
</Context.Provider>
</div>
)
}
}
export default App;
Userlist.js ( should be cryptolist or something )
import { Context } from '.././Context'
export default class Userlist extends Component {
render() {
return (
<main>
<Context.Consumer>
{(context) => context.cryptolist.map(el => {
return (
<div>
<h2>{el.name}</h2>
<h5>{el.symbol}</h5>
<h3>{el.quotes.USD.price}</h3>
</div>
)
})}
</Context.Consumer>
</main>
)
}
}
Context.js
import React from 'react';
export const Context = React.createContext();
Everything works just fine here untill i wanted to make a menu that links to this component.
import React from "react";
import { slide as Slider } from 'react-burger-menu';
import { BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";
import Main from './main';
import Userlist from './userlist';
export default class Menu extends React.Component {
render () {
return (
<Router>
<div className="bg-navy w-100 h-100">
<Slider width={ 180 } isOpen={ false }>
<Link className="menu-item" to="/main">Home</Link>
<Link className="menu-item" to="/crypto">About</Link>
</Slider>
<Switch>
<Route path="/main" component={Main} />
<Route path="/crypto" component={Userlist} />
</Switch>
</div>
</Router>
);
}
}
When i click a link to component Userlist i get an error thats cryptolist is not defined. I get it that Userlist can't see a context after clicking link to it. How to pass it correctly?
You are using the routes in the Menu component. Is this really you want? Though, I don't know how this slide thingy works. Maybe this is the way you want to go. I think your problem occurs because your Menu component is not wrapped by the provider. Try like this:
<Context.Provider value={this.state}>
<Menu />
<Userlist />
</Context.Provider
Your Menu component will call Userlist but as it is out the Provider the context doesn’t exist!
Replace Userlist in Context.Provider by Menu and all will be fine.
Related
I need some help to solve the following issue with using React.
In some web app I have a landing page, where I want to redirect the user to the login page in case she or he is not logged in.
I want to use the following landing page (taken from some tutorial I found on the net) in order to use it as a model for mine.
The problem is that this is a function component while my landing page is a class component. According to what I understand I guess I need to consider the code inside useEffect and (somewhat) transfer it to componentDidMount() in my class component. But I don't know how to do that. history.replace will not work in a class component (no Hooks in Classes). Any advice from a more React experienced user will be very welcome.
import React, { useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useHistory } from "react-router";
import "./Dashboard.css";
import { auth, db, logout } from "./firebase";
....
function Dashboard() {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const history = useHistory();
....
useEffect(() => { // Important part for my question !
if (loading) return;
if (!user) return history.replace("/");
....
}, [user, loading]);
return (
<div>
{/*...*/}
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
);
}
export default Dashboard;
Here is what I tried on my Class Component:
class MyCompo extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(MyCompo)--");
const { history } = this.props
history.push("/login");
}
.....
}
But I get the following error:
TypeError: history is undefined
componentDidMount
=============== Added information ===============
Below is the relevant part of the code I have been working on:
This part is what works:
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
What I tried in the Links Component did not work.
The code:
....
ReactDOM.render(
<Router>
<Switch>
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
<Route exact path="/login" component={Login} />
<Route exact path="/section1" component={Section1Page}/>
<Route exact path="/section2" component={Section2Page}/>
<Route exact path="/section3" component={Section3Page}/>
</Switch>
</Router>,
document.getElementById('root')
);
....
const TopMenu = () => {
return (
<div className='page_container'>
<Title/>
<Links path='/'/>
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
)
};
class Links extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(Links)--");
// This is some code I tried with no success.
const { history } = this.props
//history.push("/login");
}
componentDidUpdate(prevProps, prevState) {
console.log("--componentDidUpdate(Links)--");
}
render() {
return (
<div className='links_container'>
{(this.props.path != '/mng') &&
<React.StrictMode>
<Link to='/mng'>{mnMgrStr()}</Link><br/>
</React.StrictMode>}
{(this.props.path != '/other') &&
<React.StrictMode>
<Link to='/other'>{otherInpStr()}</Link><br/>
</React.StrictMode>}
.......
</div>
)
}
}
Following the example on the React Router docs you can use withRouter if your component isn't already receiving the route props, otherwise you can access history from the props.
class MyComponent extends React.Component {
...
componentDidMount() {
const { history } = this.props
// do whatever with history here
}
...
}
In react-router-dom version 5 there are a couple ways a class component can access the history object.
Rendered directly by a Route component via the component, or render or children function props so route props (i.e. history, location, and match) are passed.
component: <Route path="....." component={MyCompo} />
render: <Route path="....." render={routeProps => <MyCompo {...routeProps} />} />
Access the history object from the passed route props:
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
Decorated by the withRouter Higher Order Component so the route props are injected.
import { withRouter } from 'react-router-dom';
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
export default withRouter(MyCompo);
Well I hope by answering this question I can save lot of time of others. Don't need to panic it's not a major issue. I will explain step by step reason and solution.
First of all why this happening is
In react-router-dom **V6 (version 6) latest ** there is no history export or redirect.
There is navigate construct.
So to achieve in functional component there is useNavigate() hook.
Now coming to answer...
To redirect in class component using react-router-dom V6 we have to use component.
So now one has to follow the following steps:
Import navigate
import { Navigate } from "react-router-dom";
Use Navigate to redirect
So above I discussed syntax to do so now coming to your exact problem
You have to redirect user to login if he is not logged in
You can follow these steps:
create state to store status of user like logged in or not (inside constructor of class)
this.state = {
userLogged: false,
};
in your render method you have to add some condition like if user is not logged in take user to login page. see below..
render() {
const { userLogged } = this.state;
if (goToPay) {
return (
<Navigate to="/cart" state={selectedTiffin} props={selectedTiffin} />
);
}
}
That's it.
It can be confusing so I am giving full example so you can save your lot of time..
import React from "react";
import { Navigate } from "react-router-dom";
class Solve extends React.Component {
constructor(props) {
super(props);
this.state = {
userLogged: false,
};
}
// here you can write code to set the status of user like logged in or not
render() {
const { userLogged } = this.state;
if (userLogged ) {
return (
<Navigate to="/cart" />
);
}
return (
<>
Here you can return your original component that should be render when user is log in
</>
);
}
}
I hope this will help and work. Thank You
I have a component (SearchFilter.js) and am using connect to trigger mapStateToProps and mapActionsToProps on export.
Trouble is, mapStateToProps isn't firing -- no props (neither state nor actions) show up in React DevTools and I can't even console log from inside mapStateToProps.
I've tried looking at various Stack Overflow threads but they mostly seem to be typos, or the actions themselves not working.
What's more, I've got an almost identical redux setup for another component (Counter.js) that woks perfectly.
I think it could have something to do with how I provide the store/route to components (see App.js below) as React.Provider shows up in React DevTools for the Counter but not SearchFilter.
Here's the SearchFilter component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { addSearchTerm } from "../redux/actions/searchActions";
import "../styles/SearchFilter.css";
export class SearchFilter extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: "",
showFilters: false,
};
}
//various content...
}
const mapStateToProps = (state) => {
console.log(state);
return {
search: state.search,
};
};
const mapActionsToProps = {
addSearchTerm,
};
export default connect(mapStateToProps, mapActionsToProps)(SearchFilter);
App.js
function App() {
return (
<Router>
<Provider store={store}>
<div className="App">
<NavBar />
<Counter />
<Switch>
<Route exact path="/" component={Home} /> // SearchFilter rendered in Home page
<Route path="/account" component={Account} />
</Switch>
</div>
</Provider>
</Router>
);
}
EDIT: Where I've implemented this component in the Home.js view:
export default function Home() {
return (
<div>
<h4>This is the Home page</h4>
<SearchFilter />
<ProfilesList />
</div>
);
}
Try to remove the "export" when you declare the class component, maybe that helps.
change
export class SearchFilter extends Component
to
class SearchFilter extends Component
Try using bindActionCreators from redux library - https://redux.js.org/api/bindactioncreators
To dispatch any action from your component, update mapActionsToProps const in your SearchFilter component.
const mapActionsToProps = (dispatch) => bindActionCreators({
addSearchTerm},dispatch);
};
I am passing data from my child component (Header component) to my parent component (App component). My idea: when I click on my creating button in header component I want to send information to app component to hide header and footer. Here is code example.
Header.jsx:
import React, { Component } from 'react';
import { Nav, Navbar, Button } from "react-bootstrap";
class Header extends Component {
constructor(props) {
super();
this.state = {
showHeaderAndFooter: false
};
}
onChangeChildComponent = () => {
this.props.changeVisibilityOfHeaderAndFooter(this.state.showHeaderAndFooter);
}
render() {
return (
<Navbar bg="dark" variant="dark">
<Nav className="mr-auto">
<Button href="/createGym" onClick={this.onChangeChildComponent.bind(this)}> Create </Button>
</Nav>
</Navbar>
);
}
}
export default Header;
App.js:
import React, { Component, Fragment } from 'react';
import './App.css';
import './components/header';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Header from './components/header';
import Footer from './components/footer';
import Home from './components/home';
import CreateGym from './components/create-gym';
import Login from './components/authentication/login';
class App extends Component {
constructor() {
super();
this.state = {
showHeaderAndFooter: true
};
}
onChangeChildComponent (showHeaderAndFooter) {
this.setState(
{
showHeaderAndFooter: showHeaderAndFooter
});
}
Container = () => (
<div>
{ this.state.showHeaderAndFooter &&
<Header
changeVisibilityOfHeaderAndFooter = {this.onChangeChildComponent.bind(this)}
/>
}
<div className="container">
<Route exact path='/' component={Home} />
<Route path="/createGym" component={CreateGym} />
<Route path="/login" component={Login} />
</div>
{ this.state.showHeaderAndFooter && <Footer /> }
</div>
)
render() {
console.log(this.state.showHeaderAndFooter);
return (
<BrowserRouter>
<Switch>
<Fragment>
{ <Route component={this.Container}/> }
</Fragment>
</Switch>
</BrowserRouter>
);
}
}
export default App;
The problem is because my code is entering App constructor twice. At the first time, everything is fine, but on the second time, this boolean showHeaderAndFooter is again set to true because that is the default value. Any idea how to solve this?
you shouldn't be passing from child > parent. react is uni-directional (that means data can only flow in one way, and that way is downwards)
to achieve this, move the state into the parent
class Parent extends React = {
this.state = { showHeaderAndFooter: false }
functionToToggleShowing = () => {
this.setState({showHeaderAndFooter: !this.state.showHeaderAndFooter})
}
render() {
return(
<Child showHeaderAndFooter={this.state.showHeaderAndFooter} functionToToggleShowing={functionToToggleShowing} />
)
}
}
that is pseduo code but essentially move the state to the parent and pass down the state to the child as well as a way to change that state
React allows passing of control from child to parent by means of props.
Here is the 3-step procedure to make it work:
Step 1: Create a data member in Parent, which manipulates parent state.
Step 2: Send the parent's data member as a prop to the child.
Step 3: In the child, Call the prop sent by parent for responding to an event.
Here is a demonstration of passing control from child to parent, using the above technique:
The example has 2 components -
Parent component is App.jsx
Child component is Header.jsx
Parent has a state-manipulating data member called 'hideHeader'.
Parent passes hideHeader as a prop called onClick to the child.
Child calls the prop sent by parent, in response to its onClick event.
Parent component - App.jsx
import React, { Component } from 'react'
import Header from "./Header";
export default class App extends Component {
constructor() {
super();
this.state = {
showHeader: true
}
}
{/* A state-manipulating data member */}
hideHeader = () => {
this.setState({showHeader: false})
}
render() {
return (
<div>
{/* State-manipulating data member sent as prop to child */}
{ this.state.showHeader &&
<Header onClick={this.hideHeader} />
}
</div>
)
}
}
Child component - Header.jsx
import React, { Component } from "react"
export default class Header extends Component {
render() {
return (
<div>
<h1>Header Demo</h1>
{/* Call the prop sent by parent when button is clicked */}
<button onClick={this.props.onClick}>Hide</button>
</div>
)
}
}
Output:
Before 'Hide' button is clicked:
After 'Hide' button is clicked:
I'm having an attribute in one of the components and when I'm trying to access that attribute via props, I'm getting its value as undefined.
Below is the piece of code where I'm making use of the component and passing the required attribute.
import React, { Component } from "react";
import PageNotFound from "./pages/page-not-found";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import BookSectionPage from "./pages/books-section";
import BookDetails from "./pages/book-details";
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<Switch>
<Route path="/" exact component={BookSectionPage}/>
<Route path="/book/category/:categoryName" exact render = { (props) => {
return <BookSectionPage title = "JavaScript" /> // This is the component
}} />
<Route path="/book/:bookID" exact component={BookDetails} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
</div>
);
}
}
export default App;
Below is the code for the component where I'm trying to access the above-mentioned attribute via Props but getting its value as undefined.
import React from "react";
import Header from "../components/header/header";
import Footer from "../components/Footer/footer";
import BookSectionComponent from "../components/books-section/books-section";
const BookSectionPage = (Props) => {
let books=[1,2,3,4,5,6];
console.log(Props.title); // Here instead of printing the value of attribute, it's showing undefined.
return (
<div className="has-fixed-footer">
<Header />
<BookSectionComponent title = {Props.title} books = {books} />
<Footer />
</div>
);
};
export default BookSectionPage;
Any help would be highly appreciated. Thanks!
From Parent to Child Using Props
App
└── Parent
├── Child1
Most easiest direction of data flow in React and basic example.
Parent Component
class Parent extends React.Component {
state = { title : "JavaScript" }
render() {
return (
<div>
<Child1 dataFromParent = {this.state.title} />
</div>
);
}
}
Child Component
class Child1 extends React.Component {
render() {
return (
<div>
The data from parent is:{this.props.dataFromParent}
</div>
);
}
}
I'm new to React and I'm still learning it. I'm doing a personal project with it.
Let me explain my problem:
I have a component called <NewReleases /> where I make an ajax call and take some datas about some movies out on cinemas today. (I take title, poster img, overview etc...) I put all the data in <NewReleases /> state, so that state becomes an object containing an object for each movie and each object contains title poperty, poster property etc... Then I render the component so that it looks like a grid made by movies posters, infos and so on. And this works well.
Then I need a component <Movie /> to take some datas from the state of <NewReleases /> and render them on the HTML. I read other questions where people were having a similar problem, but it was different because they had a children component that was rendered by the parent component. And in that way, people suggested to pass state as props. I can't do that because my <Movie /> component is not rendered by <NewReleases />. <NewReleases /> makes the ajax call and only renders a JSX grid based on the retrieved data.
On index.js I have setup the main page this way:
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Switch, Route} from 'react-router-dom';
import {Home} from './home';
import {Movie} from './movie';
import './css/index.css';
class App extends React.Component {
render() {
return(
<BrowserRouter>
<Switch>
<Route path={'/movie/:movieTitle'} component={Movie} />
<Route path={'/'} component={Home} />
</Switch>
</BrowserRouter>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
(You can't see <NewReleases /> here because it is rendered inside of <Home /> component, which also renders a header and a footer.)
So when I click on a movie rendered by <NewReleases />, the app will let me go on localhost:3000/movie/:movieTitle where :movieTitle is a dynamic way to say the title of the movie (so for example if I click the poster of Star Wars rendered by <NewReleases />, I will go on localhost:3000/movie/StarWars). On that page I want to show detailed infos about that movie. The info are stored in <NewReleases /> state but I can't have access to that state from <Movie /> (I guess).
I hope you got what I want to achieve. I don't know if it is possible. I had an idea: on the <Movie /> I could do another ajax call just for the movie that I want but I think it would be slower and also I don't think it would be a good solution with React.
Note that I'm not using Redux, Flux etc... only React. I want to understand React well before to move to other technologies.
The way you wanna do is more complicated. With parents componentes that's easy to do. And with Redux is much more easy.
But, you wanna this way. I think if you have a state in the app, pass to home a props to set a movie-state and pass this movie-state to component Move, works fine.
The problem is that Route does't pass props. So there is a extensible route you can do. In the code below I get from web this PropsRoute.
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Switch, Route} from 'react-router-dom';
import {Home} from './home';
import {Movie} from './movie';
import './css/index.css';
class App extends React.Component {
constructor() {
super();
this.state = {
movie: {}
}
this.setMovie = this.setMovie.bind(this);
}
setMovie(newMovie) {
this.setState({
movie: newMovie
});
}
render() {
return(
<BrowserRouter>
<Switch>
<PropsRoute path={'/movie/:movieTitle'} movie={this.state.movie} component={Movie} />
<PropsRoute path={'/'} component={Home} setMovie={this.setMovie} />
</Switch>
</BrowserRouter>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
-----
const renderMergedProps = (component, ...rest) => {
const finalProps = Object.assign({}, ...rest);
return (
React.createElement(component, finalProps)
);
}
const PropsRoute = ({ component, ...rest }) => {
return (
<Route {...rest} render={routeProps => {
return renderMergedProps(component, routeProps, rest);
}}/>
);
}
I thinks this can solve your problem.
Here's a quick example. Store your state in a parent component and pass down the state down to your components. This example uses React Router 4, but it shows how you can pass down the setMovie function and movie information via state to one of your child components. https://codepen.io/w7sang/pen/owVrxW?editors=1111
Of course, you'll have to rework this to match your application, but a basic run down would be that your home component should be where you're grabbing your movie information (via AJAX or WS) and then the set function will allow you to store whatever information you need into the parent component which will ultimately allow any child components to access the information you have stored.
const {
BrowserRouter,
Link,
Route,
Switch
} = ReactRouterDOM;
const Router = BrowserRouter;
// App
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
movie: {
title: null,
rating: null
}
};
this.setMovie = this.setMovie.bind(this);
}
setMovie(payload) {
this.setState({
movie: {
title: payload.title,
rating: payload.rating
}
});
}
render(){
return(
<Router>
<div className="container">
<Layout>
<Switch>
<Route path="/select-movie" component={ () => <Home set={this.setMovie} movie={this.state.movie} />} />
<Route path="/movie-info" component={()=><MovieInfo movie={this.state.movie}/>} />
</Switch>
</Layout>
</div>
</Router>
)
}
}
//Layout
const Layout = ({children}) => (
<div>
<header>
<h1>Movie App</h1>
</header>
<nav>
<Link to="/select-movie">Select Movie</Link>
<Link to="/movie-info">Movie Info</Link>
</nav>
<section>
{children}
</section>
</div>
)
//Home Component
const Home = ({set, movie}) => (
<div>
<Movie title="Star Wars VIII: The Last Jedi (2017)" rating={5} set={set} selected={movie} />
<Movie title="Star Wars VII: The Force Awakens (2015)" rating={5} set={set} selected={movie} />
</div>
)
//Movie Component for displaying movies
//User can select the movie
const Movie = ({title, rating, set, selected}) => {
const selectMovie = () => {
set({
title: title,
rating: rating
});
}
return (
<div className={selected.title === title ? 'active' : ''}>
<h1>{title}</h1>
<div>
{Array(rating).fill(1).map(() =>
<span>★</span>
)}
</div>
<button onClick={selectMovie}>Select</button>
</div>
)
}
//Movie Info
//You must select a movie before movie information is shown
const MovieInfo = ({movie}) => {
const {
title,
rating
} = movie;
//No Movie is selected
if ( movie.title === null ) {
return <div>Please Select a Movie</div>
}
//Movie has been selected
return (
<div>
<h1>Selected Movie</h1>
{title}
{Array(rating).fill(1).map(() =>
<span>★</span>
)}
</div>
)
}
ReactDOM.render(<App />,document.getElementById('app'));
nav {
margin: 20px 0;
}
a {
border: 1px solid black;
margin-right: 10px;
padding: 10px;
}
.active {
background: rgba(0,0,0,0.2);
}
<div id="app"></div>
Create a manual object store to get/set the movie information and use it. That's it. Try the following code. That should answer all your questions. Click on any of the new releases, it will redirect to movie info screen with all the details. If you feel bad about the new releases data always refreshing, you may have to create another store, then get/set the data by checking the data exist in store.
Note: Using store and using title(duplicates may occur) in browser URL makes some problems when user refreshes the browser. For that, use id in browser URL, fetch the details using AJAX call and set that details in store.
//store for movie info
const movieInfoStore = {
data: null,
set: function(data) {
this.data = data;
},
clear: function() {
this.data = null;
}
};
class MovieInfo extends React.Component {
componentWillUnmount() {
movieInfoStore.clear();
}
render() {
return (
<div>
<pre>
{movieInfoStore.data && JSON.stringify(movieInfoStore.data)}
</pre>
<button onClick={() => this.props.history.goBack()}>Go Back</button>
</div>
)
}
}
MovieInfo = ReactRouterDOM.withRouter(MovieInfo);
class NewReleases extends React.Component {
handleNewReleaseClick(newRelease) {
movieInfoStore.set(newRelease);
this.props.history.push(`/movie/${newRelease.title}`);
}
render() {
const { data, loading } = this.props;
if(loading) return <b>Loading...</b>;
if(!data || data.length === 0) return null;
return (
<ul>
{
data.map(newRelease => {
return (
<li onClick={() => this.handleNewReleaseClick(newRelease)}>{newRelease.title}</li>
)
})
}
</ul>
)
}
}
NewReleases = ReactRouterDOM.withRouter(NewReleases);
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
newReleases: [],
newReleasesLoading: true
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
newReleases: [{id: 1, title: "Star Wars"}, {id: 2, title: "Avatar"}],
newReleasesLoading: false
});
}, 1000);
}
render() {
const { newReleases, newReleasesLoading } = this.state;
return (
<NewReleases data={newReleases} loading={newReleasesLoading} />
)
}
}
class App extends React.Component {
render() {
const { BrowserRouter, HashRouter, Switch, Route } = ReactRouterDOM;
return (
<HashRouter>
<Switch>
<Route path="/movie/:movieTitle" component={MovieInfo} />
<Route path="/" component={Home} />
</Switch>
</HashRouter>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script>
<div id="root"></div>