I'm new to Redux, using it with React, and am in need of some help. I have a menu that when a menu item is clicked, another component needs to update some copy. I'm able to dispatch an action and have the store update. However, I can't get the child component (HeroText) to render the new store value in the store.subscribe method when the store values change. Please help and thanks!
import React, { Component } from "react";
import ReactDOM from "react-dom";
import HeroText from "../presentational/HeroText.jsx";
import bgImage from "../../../images/forest_fog.jpg";
import AnantaNavbar from "../presentational/AnantaNavbar.jsx";
import '../../../scss/hero.scss';
import store from '../../store/index';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
contact: state.contact,
heroText: state.heroText
}
}
class HeroContainer extends Component {
constructor(props)
{
super(props);
this.state = store.getState();
store.subscribe(() => {
console.log(store.getState().heroText);
this.setState({
heroText: store.getState().heroText,
})
})
}
render()
{
return (
<div id="hero-container" style={{backgroundImage: ("url(" + bgImage + ")") || ""}}>
<div className="container">
<HeroText text={this.props.heroText}>
Welcome back {this.props.contact.full_name}
</HeroText>
<AnantaNavbar></AnantaNavbar>
</div>
</div>
);
}
}
export default connect(mapStateToProps)(HeroContainer);
UPDATE
Below is my parent App Container with Provider
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Navbar, NavbarBrand, NavbarNav, NavbarToggler, Collapse, NavItem, NavLink, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'react-bootstrap';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import LoginContainer from '../../../js/components/container/LoginContainer.jsx';
import DashboardContainer from '../../../js/components/container/DashboardContainer.jsx';
import HomeContainer from '../../../js/components/container/DashboardContainer.jsx';
import ProfileContainer from '../../../js/components/container/ProfileContainer.jsx';
import HeroContainer from "./HeroContainer.jsx";
import '../../../scss/globals.scss';
import logo from '../../../images/logo1.png';
import { Provider } from 'react-redux';
import store from '../../store/index';
const Router = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={LoginContainer} />
<Route exact path="/login" component={LoginContainer} />
<Route exact path="/home" component={HomeContainer} />
<React.Fragment>
<HeroContainer />
<Route path="/dashboard" component={DashboardContainer} />
<Route path="/profile" component={ProfileContainer} />
</React.Fragment>
</Switch>
</BrowserRouter>
);
class AppContainer extends Component {
constructor(props)
{
super(props);
this.state = {
};
}
componentDidMount()
{
}
render()
{
return (
<div>
<Provider store={store}>
<Router></Router>
</Provider>
</div>
);
}
}
export default AppContainer;
The default heroText in the store says "DASHBOARD". When a menu item is clicked, in this case a link to /profile, the heroText should update to "PROFILE" after updating the store.
You can see in the console that the store is changing, but the "DASHBOARD" copy is not reflecting.
RESOLVED
I got this working with the code below. Thanks for all the help!
import React, { Component } from "react";
import ReactDOM from "react-dom";
import HeroText from "../presentational/HeroText.jsx";
import bgImage from "../../../images/forest_fog.jpg";
import AnantaNavbar from "../presentational/AnantaNavbar.jsx";
import '../../../scss/hero.scss';
import store from '../../store/index';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
contact: state.contact,
heroText: state.heroText
}
}
class HeroContainer extends Component {
constructor(props)
{
super(props);
}
render()
{
return (
<div id="hero-container" style={{backgroundImage: ("url(" + bgImage + ")") || ""}}>
<div className="container">
<HeroText text={store.getState().heroText}>
Welcome back {store.getState().contact.full_name}
</HeroText>
<AnantaNavbar></AnantaNavbar>
</div>
</div>
);
}
}
export default connect(mapStateToProps)(HeroContainer);
Since you are trying to get state from Redux, there's no pointing in keeping it in local state. Plus, you don't need to use store.getState, connect already does that for you.
const mapStateToProps = state => {
return {
contact: state.contact,
heroText: state.heroText
}
}
class HeroContainer extends Component {
render() {
return (
<div id="hero-container" style={{backgroundImage: ("url(" + bgImage + ")") || ""}}>
<div className="container">
<HeroText text={this.props.heroText}>
Welcome back {this.props.contact.full_name}
</HeroText>
<AnantaNavbar></AnantaNavbar>
</div>
</div>
);
}
}
export default connect(mapStateToProps)(HeroContainer);
You also need to make sure that your app is wrapped in a provider, like this:
<Provider store={store}>
<App />
</Provider>
Related
I'm testing some function, I discover a solution but I'm not sure that is correct way.
I need to load data from API so I used axios, and I need to share data between some child routes, I am currently using simple setState to keep values and passed them to the Route Components to be used.
Here is the MainContainer component
import React from 'react';
import axios from 'axios';
import { BrowserRouter, Route, Switch, Link, HashRouter, useLocation } from 'react-router-dom';
import Languages from './pages/Languages';
import Home from './pages/Home';
export default class MainContainer extends React.Component
{
constructor(props) {
super(props);
this.state = {
languages: null,
language:null,
}
}
get_init(language)
{
console.log("get_init")
var params = {
language:language,
}
axios
.get("https://xxxx.com/wp/it/api/init/")
.then(function(result) {
this.setState({languages:result.data.languages});
}.bind(this));
}
render() {
console.log("render")
return (
<div>
<Switch>
<Route path="/" component={() => <Languages get_init={this.get_init.bind(this)} languages={this.state.languages} />} exact />
<Route path="/:language/" component={(urlparam) => <Home language={urlparam.match.params.language} get_init={this.get_init.bind(this)} languages={this.state.languages} />} exact/>
<Route component={Error}/>
</Switch>
</div>
);
}
}
My Language Component is bellow
import axios from 'axios';
import React from 'react';
import LoaderData from './../helpers/LoaderData';
import { Link } from 'react-router-dom';
export default class Languages extends React.Component
{
constructor(props)
{
super(props);
if (props.languages==null)
{
this.props.get_init();
}
}
render() {
if (this.props.languages==null)
{
return '';
}
return (
<div>
<div className='main'>
LANGUAGE {Math.random()}<br/>
{
this.props.languages.map((language, i) =>
{
return (<div key={language.prefix}><Link to={language.prefix+"/"} >{language.name}</Link> <br/></div>)
})
}
</div>
</div>
);
}
}
is a correct way to build a website in react or there are better ways?
I want to try to transfer the record from state through several components using Consumer and Provider. I have a value in the ShoppingBasketProvider component that I want to pass to the ShoppingBasket component, but at this point it doesn't output anything and equals undefined. Tell me what I'm doing wrong.
I have next components:
ShoppingBasketContext:
import React, { Component, PureComponent } from "react";
export const ShoppingBasketContext = React.createContext();
export default class ShoppingBasketProvider extends Component {
state = {
shoppingBasketItems: 11
}
render() {
return (
<ShoppingBasketContext.Provider shoppingBasketItems={ this.state.shoppingBasketItems }>
{this.props.children}
</ShoppingBasketContext.Provider>
)
}
}
app:
import React, {PureComponent} from 'react'
import './App.scss';
import Shop from '../../page/shop/shop.js'
import AboutUs from '../../page/aboutUs/aboutUs.js'
import Menu from '../menu/menu.js'
import ShoppingBasketProvider from '../shoppingBasketProvider/shoppingBasketProvider.js'
import { BrowserRouter, Switch, Route, Redirect, Router } from "react-router-dom";
export default class App extends PureComponent {
render() {
return (
<main>
<ShoppingBasketProvider>
<BrowserRouter>
<Menu />
<Switch>
<Route path="/" exact children={<Shop />} />
<Route path="/aboutus" children={<AboutUs />} />
<Redirect to="/" />
</Switch>
</BrowserRouter>
</ShoppingBasketProvider>
</main>
);
}
}
ShoppingBasket:
import React, {PureComponent} from 'react'
import { ShoppingBasketContext } from '../shoppingBasketProvider/shoppingBasketProvider.js';
export default class ShoppingBasket extends PureComponent {
render() {
return (
<div>
<ShoppingBasketContext.Consumer>
{shoppingBasketItems => (
<React.Fragment>
Shopping cart: <span>{shoppingBasketItems}</span>
</React.Fragment>
)}
</ShoppingBasketContext.Consumer>
</div>
)
}
}
The prop you pass to the provider must be called value. See suggested change below
return (
<ShoppingBasketContext.Provider value={ this.state.shoppingBasketItems }>
{this.props.children}
</ShoppingBasketContext.Provider>
)
I have a NavBar that's on multiple pages that allows me to search for an user. After the searching, clicking on it should re-direct me to the user page via Link from react-router-dom. The problem is this only works on the main page, and doesn't work when clicking from an user's page.
I've tried adding WithRouter to all my related components (containers, parents, and the children). Also tried fiddling with the Route pathing / exact pathing syntax to no avail.
*Removed components imports and non-relavant code for clarity.
/app.jsx
import React from 'react';
import { Provider } from 'react-redux';
import {
Route,
Redirect,
Switch,
Link,
HashRouter,
withRouter,
} from 'react-router-dom';
import { AuthRoute, ProtectedRoute } from '../utils/route_utils';
const App = () => (
<div className="main">
<HashRouter>
<Switch>
<AuthRoute exact path="/" component={LoginContainer} />
<AuthRoute exact path="/signup" component={SignupContainer} />
<ProtectedRoute exact path="/feed" component={FeedContainer} />
<ProtectedRoute exact path="/users/:userId" component={GreetingContainer} />
<ProtectedRoute exact path="/posts/:postId" component={PostShowContainer} />
<ProtectedRoute exact path="/upload" component={Upload} />
<ProtectedRoute exact path="/accounts/edit" component={AccountsEditContainer} />
<Route component={Errors} />
</Switch>
</HashRouter>
</div>
);
export default withRouter(App);
/greeting_container.jsx
import React from 'react';
import { withRouter } from 'react-router-dom';
const mapStateToProps = (state, ownProps) => {
...
};
const mapDispatchtoProps = (dispatch) => ({
....
});
export default withRouter(connect(mapStateToProps, mapDispatchtoProps)(Greeting));
/greeting.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import NavBarContainer from '../nav_bar/nav_bar_container';
class Greeting extends React.Component {
constructor(props) {
super(props);
this.renderPosts = this.renderPosts.bind(this);
}
render() {
if (!this.props.user) return null;
const profileUser = parseInt(this.props.match.params.userId)
const user = this.props.user
return (
<div className="user-page-container">
<Modal currentUser={user} />
<NavBarContainer />
</div>
}
export default Greeting;
/nav_bar_container.jsx
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import NavBar from './nav_bar';
const mapStateToProps = (state) => {
....
};
const mapDispatchToProps = (dispatch) => ({
...
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(NavBar));
/nav_bar.jsx
import React from 'react';
import { Link, withRouter } from 'react-router-dom';
import NavBarSearch from './nav_bar_search';
const NavBar = (props) => {
return (
<div>
< NavBarSearch
fetchSearchedUsers={props.fetchSearchedUsers}
searchResults={props.searchResults}
/>
</div>
);
};
export default withRouter(NavBar);
/nav_bar_search.jsx
import React from 'react';
import NavBarSearchResults from './nav_bar_search_results';
import { withRouter } from 'react-router-dom';
class NavBarSearch extends React.Component {
constructor(props) {
super(props);
this.state = {
searchInput: ''
}
this.generateSearch = this.generateSearch.bind(this)
}
render() {
return (
<div >
<input className="search-bar-test" onChange={this.generateSearch} type="text" placeholder='Search' />
{this.state.searchInput ? <NavBarSearchResults
searchResults={this.props.searchResults}
handleClick={this.handleClick}
// key={this.props.searchResults.id}
/> : null
}
</div>
)
}
}
export default withRouter(NavBarSearch);
/nav_bar_search_results.jsx
import React from 'react';
import { withRouter, Link } from 'react-router-dom';
const NavBarSearchResults = function({ searchResults, handleClick }) {
let results = searchResults ? Object.entries(searchResults).map((result) =>
<Link className="search-links" to={`/users/${result[0]}`} onClick={e => handleClick} key={result[0]}>
<div className="search-results-item">
<img className="search-results-image" src={result[1].photoUrl} />
{result[1].username}
</div>
</Link>
) : null
return (
<div className="search-results-container">
{results}
</div>
);
}
export default withRouter(NavBarSearchResults);
Once again, it is the Link in the Nav_Bar_Search_Results.jsx file that is not working. This file is nested several levels deep starting from the Greeting.jsx file. The string interpolation I'm doing is the user_id number. Please recall that the url parameters updates and the page works if I CLICK and REFRESH page, but not on first click alone.
The correct user page should render as specified by the url parameters
I'm making a react app, I made a navbar and It renders in all of the components and I only want it visible in one, I made a HOC function but It still doesnt work correctly.
Higher Order Components
this is my navigation component
import React from 'react';
import {NavLink} from "react-router-dom";
const Navigation = () => {
return (
<div id = "navlinks">
<NavLink to = "">PROMOS</NavLink>
<NavLink to = "" >Nueva Orden</NavLink>
<NavLink to = "" >Ordenes</NavLink>
<NavLink to = "">Menú</NavLink>
<NavLink id = "logout" to = "/" >Cerrar Sesión</NavLink>
</div>
)
}
export default Navigation;
and this is is my router
import React, { Component } from 'react';
import { BrowserRouter , Route} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import Menu from "./components/Menu";
import Navigation from "./components/Navigation";
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Navigation/>
<div>
<Route path= "/" component = {Home} exact />
<Route path= "/Menu" component = {Menu}/>
</div>
</div>
</BrowserRouter>
);
}
}
export default App;
and my HOC component
import React, { Component } from 'react';
const LoaderHOC = (WrappedComponent) => {
return class LoaderHOC extends Component{
render(){
this.props.Navigation.length === 0 ? <div className = 'Loader'></div> : <WrapperComponent {... this.props}/>
}
}
}
export default LoaderHOC;
I suppose you have a way to determine whether your user is loggedIn or not. Suppose, you have store the information in isLoggedIn variable, than you can do following to hide navigation if user is not logged in,
{ isLoggedIn && <Navigation /> }
But once your application grows, I suggest you to make different routes depending on the public/private state.
Create a PrivateRoute.js file
import React, { Component } from 'react';
import { Redirect, Route } from 'react-router-dom';
import Navigation from "./components/Navigation";
class PrivateRoute extends Component {
render() {
// use your own logic here
const isLoggedIn = !!localStorage.getItem('token');
if (!isLoggedIn) {
return <Redirect to='/' />;
}
return (
<div>
<Navigation />
// your private route
</div>
}
}
export default PrivateRoute;
create your PublicRoute.js file
import React, { Component } from 'react';
import { Redirect, Route } from 'react-router-dom';
class PublicRoute extends Component {
render() {
<div>
// your all public route
</div>
}
}
export default PublicRoute;
Now Just include those into your main file
import React, { Component } from 'react';
import { BrowserRouter , Route} from "react-router-dom";
import { PublicRoute, PrivateRoute } from './routes';
import './App.css';
import Home from "./components/Home";
import Menu from "./components/Menu";
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<PublicRoute />
<PrivateRoute />
</div>
</BrowserRouter>
);
}
}
export default App;
Don't use HOC for this.
You must have store somewhere that user is loggedIn, if not I would suggest you to use a localStorage like,
localStorage.setItem("loggedIn", true);
Note: To setup a localStorage you don't need any extra configuration.
In your router you can use this to hide your component,
{localStorage.getItem("loggedIn") && <Navigation/>}
I have a categories index page which links to a products index page of products specific to that category. That much is functioning. But when I attempt to click on a product linked to a show component specific to that product I'm encountering trouble. Below is my code:
router.js
import React from 'react';
import { Router, Route, Switch } from 'react-router';
import createBrowserHistory from 'history/createBrowserHistory'
import App from './App';
import CategoriesIndexPage from './pages/categories/CategoriesIndexPage';
import ProductsIndexPage from './pages/products/ProductsIndexPage';
import ProductShow from './pages/products/ProductShow';
import LocationsPage from './pages/LocationsPage';
const history = createBrowserHistory()
const router = (
<Router history={history}>
<Switch>
<Route exact path='/' component={App}/>
<Route path='/categories' component={CategoriesIndexPage}/>
<Route path='/locations' component={LocationsPage}/>
<Route path='/:category' component={ProductsIndexPage}>
<Route path='/:id' component={ProductShow}/>
</Route>
</Switch>
</Router>
);
export default router;
ProductIndexPage.js
import React, { Component } from 'react';
import { BWReactData } from '../../config/FirebaseConstants.js';
import Head from '../../components/Head.js';
import Foot from '../../components/Foot.js';
import ProductsIteration from './ProductsIteration';
class ProductsIndexPage extends Component {
constructor(props){
super(props);
this.state = {
allProducts: [],
loading: true,
}
}
componentDidMount() {
...
}
render() {
let allProducts = this.state.allProducts;
let loading = this.state.loading;
let categoryURL = this.props.location.state.category;
return (
<div>
<Head/>
<ProductsIteration
allProducts={allProducts}
loading={loading}
categoryURL={categoryURL}
/>
<Foot/>
</div>
)
}
}
export default ProductsIndexPage;
ProductsIteration.js
import React from 'react';
import { Link } from 'react-router-dom';
import { Col, Row } from 'react-materialize';
const ProductsIteration = props => {
let category = props.categoryURL;
if (props.loading) {
return <div>Loading...</div>
}
return (
<Row>
{props.allProducts.map(function(object) {
return (
<Col s={12} m={6} l={3} key ={object.id}>
<div style={styles.wrapper}>
<Link to={{ pathname: `${category}/${object.id}`, state: { id: object.id }}}>
<img src={object.img} style={styles.image} />
<div style={styles.description}>
<div style={styles.descriptionContent}>{object.name}</div>
</div>
</Link>
</div>
</Col>
)
})}
</Row>
)
}
export default ProductsIteration;
The link within my iteration component renders the '/:category/:id' url in my navbar but the page does nothing. This is my first project using router and any guidance would be much appreciated.
In React Router v4:
Router components are imported from 'react-router-dom' rather than 'react-router'.
The traditional <Router/> component has been replaced with the <BrowserRouter/> component, which requires no props.
Nesting routes is no longer convention. Instead, you'll have to nest your <ProductShow/> as a component prop of a <Route/> component within a <Switch/> component within your <ProductIndexPage/> component.
See below for an example.
Router.js:
// React.
import React from 'react'
// React Router DOM.
import {
BrowserRouter as Router,
Route,
Switch
} from 'react-router-dom'
// Routes.
import App from './App'
import CategoriesIndexPage from './pages/categories/CategoriesIndexPage'
import ProductsIndexPage from './pages/products/ProductsIndexPage'
import LocationsPage from './pages/LocationsPage'
// Router.
const Router = (
<Router>
<Switch>
<Route exact path='/' component={App}/>
<Route path='/categories' component={CategoriesIndexPage}/>
<Route path='/locations' component={LocationsPage}/>
<Route path='/:category/:id?' component={ProductsIndexPage}/>
</Switch>
</Router>
)
// Export.
export default Router
ProductIndexPage.js:
// React.
import React from 'react'
// BW React Data.
import {
BWReactData
} from '../../config/FirebaseConstants.js'
// Head.
import Head from '../../components/Head.js'
// Foot.
import Foot from '../../components/Foot.js'
// Products Iteration.
import ProductsIteration from './ProductsIteration'
// Product Show.
import ProductShow from './ProductShow'
// React Router DOM.
import {
Switch
} from 'react-router-dom'
// Products Index Page.
class ProductsIndexPage extends React.Component {
// Constructor.
constructor(props){
// Super Props.
super(props)
// State.
this.state = {
allProducts: [],
loading: true,
}
}
// Did Mount.
componentDidMount() {
...
}
// Render.
render() {
let allProducts = this.state.allProducts
let loading = this.state.loading
let categoryURL = this.props.location.state.category
return (
<div>
<Head/>
<ProductsIteration
allProducts={allProducts}
loading={loading}
categoryURL={categoryURL}
/>
{this.props.match.params.id ? (<ProductShow/>) : ''}
<Foot/>
</div>
)
}
}