How to pass `props` data from parent to child on react router - javascript

I want to pass my props data from parent to child when it will route to new path.
I tried some https://github.com/ReactTraining/react-router/issues/4105 but not actually worked when i passed it through {...props}.
any help would be greatful.
//App.js
class App extends Component{
constructor(props){
super(props);
}
render(){
return (
<div className="App">
<div className="navbar">
<h2 className="center">Tiny Book Library</h2>
</div>
<Switch>
<Route exact path="/" component={PostBook}/>
<Route exact path="/abc" render={props => <AllBook someProp="2" {...props} />} />
</Switch>
</div>
);
}
}
//Allbook.js
class AllBook extends Component {
constructor(props){
super(props);
}
render(){
return(
<div>
{Object.keys(this.props.posts).length !== 0 ? <h1 className="post-heading">All books</h1> : ""} {/*To check if array is empty or not*/}
{/*Arrow function to map each added object*/}
{this.props.posts.map((post) =>(
<div key={post.id}>
{post.editing ? <EditComponent post={post} key={post.id}/> :
<Post key={post.id} post={post}/>}
</div>
))}
</div>
);
}
}
const mapStateToProps = (state) => {
return{
posts: state
}
}
export default connect(mapStateToProps)(AllBook);
//reducer
const postReducer = (state = [], action) => {
switch(action.type){
case 'ADD_BOOK':
return state.concat([action.data]);
case 'DELETE_BOOK':
return state.filter((post) => post.id !== action.id);
case 'EDIT_BOOK':
return state.map((post)=>post.id === action.id ? {...post, editing:!post.editing} : post)
case 'UPDATE':
return state.map((post)=>{
if(post.id === action.id){
return{
...post,
title: action.data.newTitle,
number:action.data.newNumber,
author:action.data.newAuthor,
description:action.data.newDescription,
editing: !post.editing
}
}
else return post;
})
default:
return state;
}
}
export default postReducer;

UPDATE #1: Adding a Link for the sake of a Correct Page Transition:
First: import Link component at the beginning of your App.js:
import { Link } from 'react-router-dom'
Second: Add Link components to route between both pages:
class App extends Component{
constructor(props){
super(props);
}
render(){
return (
<div className="App">
<div className="navbar">
<h2 className="center">Tiny Book Library</h2>
<Link to="/">Post A Book</Link>
<Link to="/abc">All Books</Link>
</div>
<Switch>
<Route exact path="/" component={PostBook}/>
<Route exact path="/abc" render={props => <AllBook someProp="2" {...props} />} />
</Switch>
</div>
);
}
}
There is probably nothing wrong with the code above, the cause of this behavior is changing the route manually from the browser.
Which causes the problem.
Explaining further:
You post the data from the form in PostBook.
The data is stored in the reducer.
You change the url manually from the browser, you lose all the data you have submitted to the reducer, this is not an error, nor an expected behavior, because doing that will request entirely new app for you, thus, ZERO DATA.
Please read about Link, to change the url properly so you can test your code whether it works or not.

As mentioned in the react-router docs, the parameters in the render function prop are the router props: i.e. the props that the Route component would normally get: history, location and more.
You don't want that, you want to pass your own/parent props:
\ App.js
class App extends Component{
constructor(props){
super(props);
}
render(){
const myProps = this.props;
return (
<div className="App">
<div className="navbar">
<h2 className="center">Tiny Book Library</h2>
</div>
<Switch>
<Route exact path="/" component={PostBook}/>
<Route exact path="/abc" render={() => <AllBook someProp="2" {...myProps} />} />
</Switch>
</div>
);
}
}

Not sure if would be helpfull.
Subscribe Allbook to posts, in your case looks like your entire redux store is just posts, or at least you are passing all redux state to posts.
class AllBook extends Component {
constructor(props){
super(props);
}
render(){
return(
<div>
{Object.keys(this.props.posts).length !== 0 ? <h1 className="post-heading">All books</h1> : ""} {/*To check if array is empty or not*/}
{/*Arrow function to map each added object*/}
{this.props.posts.map((post) =>(
<div key={post.id}>
{post.editing ? <EditComponent post={post} key={post.id}/> :
<Post key={post.id} post={post}/>}
</div>
))}
</div>
);
}
}
// (state) => { return { posts: state.posts } }; Should also work.
const mapStateToProps = ({ posts }) => ({ posts });
export default connect(mapStateToProps)(AllBook);

Related

How to correctly pass a callback and a state to the Layout element in React Router Dom?

How to correctly pass callbacks and states to the Layout so that they can be used elsewhere? When I share this as below, I have errors and a white screen:
class Menu extends Component {
constructor(props) {
super(props);
this.onSearchF = this.onSearchF.bind(this)
}
state = {
searchBlock: false,
};
onSearchF = (keyword) => {
const filtered = this.state.data.filter((entry) =>
Object.values(entry).some(
(val) => typeof val === "string" && val.toLowerCase().includes(keyword.toLowerCase())
)
);
};
render() {
return (
<div className="content">
<Routes>
<Route path="/" element={<Layout searchBlock={this.state.searchBlock} onSearch={()=>this.onSearchF()}/>}>
<Route
index
element={
<Home data={this.state.data} num={this.state.data.length} />
}
/>
</Route>
</Routes>
</div>
);
}
}
export default Menu;
Here I pass the callback to the Header that I previously passed to the Layout:
const Layout = () => {
return (
<>
<Header sblock={this.props.searchBlock} onS = {this.props.onSearch}/>
</>
);
};
export default Layout;
I want to use the callback here:
function Header() {
return (
<header className="header">
<button onClick={()=>console.log(this.props.sblock)}>button</button>
</header>
);
}
export default Header;
Your Layout is a functional component, and you are trying to use this.props in it; this is incorrect. Get the props as part of arguments instead, like so:
import { Outlet } from "react-router-dom";
const Layout = ({searchBlock,onSearch}) => {
return (
<>
<Header sblock={searchBlock} onS={onSearch}/>
<Outlet/>
</>
);
};
export default Layout;
Issues
The Layout component isn't accepting any props.
The Layout component isn't rendering an Outlet for nested routes.
Solution
It seems that Layout only exists to render the Header component. I'd suggest rendering Header directly in the Main component.
Example:
class Menu extends Component {
state = {
data: [],
searchBlock: false,
};
onSearch = (keyword) => {
const filtered = this.state.data.filter((entry) =>
Object.values(entry).some((val) =>
typeof val === "string"
&& val.toLowerCase().includes(keyword.toLowerCase())
)
);
... do something with filtered ...
};
render() {
const { data, searchBlock } = this.state;
return (
<div className="content">
<Header sblock={searchBlock} onS={this.onSearch} />
<Routes>
<Route
path="/"
element={<Home data={data} num={data.length} />}
/>
</Routes>
</div>
);
}
}
export default Menu;

React router renders component once after that only url changes

I am having a navbar and a side bar for my page.
Navbar consists of home and blogs
Blogs will render BlogHome Component which will fetch links from db and on click of any link will render BlogContent component.
Lets say the side bar has Blog1,Blog2 and Blog3 listed. If I click Blog1 it renders Blog1's content properly to its side, but if I click Blog2 again it just changes URL but not the Blog2's content.
Please take a look at my code:
Navbar.js
<Router>
<Container className="p-0" fluid={true}>
<Navbar className="border-bottom" bg="transparent" expand="lg">
<Navbar.Brand>{global.config.appname}</Navbar.Brand>
<Navbar.Toggle className="border-0" aria-controls="navbar-toggle" />
<Navbar.Collapse id="navbar-toggle">
<Nav className="ml-auto">
<Link className="nav-link" to="/">Home</Link>
<Link className="nav-link" to="/blogs/main">Blogs</Link>
<Link className="nav-link" to="/contact">Contact</Link>
</Nav>
</Navbar.Collapse>
</Navbar>
</Container>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/blogs/main" component={BlogHome}></Route>
</Switch>
</Router>
BlogHome.js
export default class BlogHome extends Component {
constructor(props)
{
super(props);
this.state = { data: null,route:null };
}
componentDidMount = () => {
console.log("BlogHome");
BlogDataService.getAll().then(data => {
let data_temp = []
let cnt = 0;
for (let item of data.data) {
data_temp.push(
<MenuItem key={cnt++} icon={<FaBlog />}>
<Link to={"/blogs/main/" + item.id}>{item.title}</Link>
</MenuItem>
);
}
this.setState({ data: data_temp });
})
}
render() {
return (
<Router>
<div style={{ display: "flex" }}>
<ProSidebar>
<Menu iconShape="square">
{this.state.data}
</Menu>
</ProSidebar>
<Switch>
<Route exact path={"/blogs/main/:blogId"} component={BlogContent}></Route>
</Switch>
</div>
</Router>
);
}
}
BlogContent.js
export default class BlogContent extends Component {
constructor(props) {
super(props);
const contentState = convertFromRaw(content);
this.state = {
contentState,
item_id: this.props.match.params.blogId,
title:null
}
console.log(this.props.match);
}
onContentStateChange: function = (contentState) => {
this.setState({
contentState,
});
};
componentDidMount = () => {
BlogDataService.get(this.state.item_id).then(data => {
console.log(data);
this.setState({ title: data.data.title })
});
}
render() {
const { contentState } = this.state;
return (
<Router>
<div style={{padding:"10px"}}>
<div style={{padding:"50px",fontSize:"50px"}}>
{this.state.title}
</div>
<Editor
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onContentStateChange={this.onContentStateChange}
/>
<Route exact path={"/blogs/main/1"} component={BlogContent}></Route>
</div>
</Router>
);
}
}
Thank you for reading :)
your item_id is set only one time and it is not changing at all. On first time when component load it will work but when you are doing second time you are passing new item id but component is not aware about this change hence not able to do anything.
Try to create a function which fetch data. Same function call it in componentDidmount.
Now when it is getting new props it is time to check . Use componentDidUpdate.
componentDidUpdate(prevProps, prevState){
if(prevProps.blogId != this.props.blogId){
this.setState({
item_id: this.props.blogId
}, () => { call the function to get the data } )
}
}

Redux store not connected

I am developing a Reactjs web application from scratch and encountered a tricky situation which i need help with. Whenever i navigate away from a particular url and navigate back, my redux store does not seem to be connected.
routes.js
const RouteList = () => (
<main>
<Switch>
<Route path="/abc/" exact component={withRouter(HomePage)} />
<Route path="/abc/xyz" exact component={withRouter(XYZPage)} />
<Redirect from="/" to="/abc/" />
<Route component={Error} />
</Switch>
</main>
);
export default RouteList;
App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render () {
return (
<Router history={browserHistory}>
<div>
<Header />
<RouteList />
<Footer />
</div>
</Router>
);
}
}
export default App;
Header.js
const Header = () => {
return (
<Navbar expand="md">
<NavbarBrand tag={NavLink} to="/">
<img src={brandImage} style={{marginRight: "0", width: "40px", height: "40px"}} /><strong style={{color: "#457B9D"}} >Datum</strong>
</NavbarBrand>
<Nav className="mr-auto" navbar>
<NavItem>
<NavLink className="nav-link" to={"/abc/xyz"} >XYZ</NavLink>
</NavItem>
</Nav>
</Navbar>
);
};
export default withRouter(Header);
When i hit the NavLink which will take me to url: /"abc/xyz", it will take me to XYZPage.js
XYZPage.js
class XYZPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
activeTab: "1"
};
this.toggle = this.toggle.bind(this);
}
toggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
render () {
return (
<main>
<div className="container-fluid pt-3">
<Nav tabs>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "1"})}
onClick={() => {this.toggle("1"); }} >
AAA
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "2"})}
onClick={() => {this.toggle("2"); }} >
BBB
</NavLink>
</NavItem>
<NavItem>
<NavLink
className={classnames({active: this.state.activeTab === "3"})}
onClick={() => {this.toggle("3"); }} >
CCC
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
<Row>
<AAAPAge/>
</Row>
</TabPane>
<TabPane tabId="2">
<Row>
<BBBPage/>
</Row>
</TabPane>
<TabPane tabId="3">
<Row>
<CCCPage/>
</Row>
</TabPane>
</TabContent>
</div>
</main>
);
}
}
export default withRouter(XYZPage);
Each of the AAAPage, BBBPage & CCCPage are components which needs to have some pre-populated dropdowns which i declared in my index.js below:
index.js
const store = configureStore();
store.dispatch(loadAAA());
store.dispatch(loadBBB());
store.dispatch(loadCCC());
render((
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
), document.getElementById('app'));
loadAAA, loadBBB & loadCCC are all thunks
The configureStore() method is as such:
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
composeWithDevTools(
applyMiddleware(thunk, reduxImmutableStateInvariant()),
)
);
}
To shorten this post i give a sample of my AAAPage as the others are of similar structure:
AAAPage.js:
class AAAPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {...};
}
componentWillReceiveProps(nextProps) {...}
render() {
[...]
return (
<Container fluid>
<Row>
<AAAInputForm
// Data from Store is passed here
/>
</Row>
{ChildComponent}
</Container>
);
}
}
AAAPage.propTypes = {
DATA: PropTypes.array
};
function mapStateToProps(state, ownProps) {
let DATA = [];
if (state.AAAReducer.length > 0) {
DATA = state.AAAReducer;
}
return {
DATA: DATA
};
}
export default withRouter(connect(mapStateToProps)(AAAPage));
AAAReducer.js:
export default function AAAReducer(state=initialState.AAAList, action) {
switch(action.type) {
case types.LOAD_AAA_SUCCESS:
return action.AAAList;
default:
return state;
}
}
AAAAction.js:
export function loadAAASuccess(AAAList) {
return {
type: types.LOAD_AAA_SUCCESS,
AAAList: AAAlList
};
}
// thunk
export function loadAAA() {
// A thunk will always return a function that accepts a dispatch
return function(dispatch) {
return apiCall("ALL").then(response => {
dispatch(loadAAASuccess(response.data.AAA));
}).catch(error => {
throw(error);
});
};
}
initialState.js:
export default {
AAAList: [],
BBBList: [],
CCCList: []
};
At this point i believe i provided enough background to my code. I followed tutorials when designing this redux store and I am not sure why when i navigate from "/abc/xyz" to "/abc" and back, or when i navigate to "/abc/xyz" from "/abc", my stores are empty although i called the loadAAA() method at my index.js. All the other pages are affected as well. However, when i hit "/abc/xyz" directly, my stores are connected and my dropdowns are populated. What is happening? Is it because of my lifecycle methods?
I am using react v15.6.2, redux v3.7.2 & redux-thunk v2.3.0.
Thanks for the guidance.
You only call loadAAA at the top level of index.js, which only executes once when your page loads. If you want to dispatch it every time your XYZPage page renders, put in XYZ's componentDidMount
#AKJ - #Andy Ray said it correctly, but I'll like to add that componentDidMount is the best place to load async calls, as it is called after render and about Store redux store keeps data until you refresh the page after refresh redux store is reinitialized, if you need store the data after refresh try redux-persist

Passing props to NavBar doesn't update component

I am building an ad website. I built a registration system, which works perfectly fine, but for some reason I can't update the NavBar based on the event that has happened. For example, I want to replace the NavLink called "LOGIN/REGISTER" with "LOGGED IN". I have passed the props of the User.ID from the parent component (App.js) into the other components without any problem, but cannot do this for the NavBar. If I try a console.log - it would say undefined. I am going to put a couple of codes demonstrating where it works and where it does not:
APP.JS
*imports, which I am skipping*
const cookies = new Cookies();
class App extends Component {
constructor(){
super();
this.state = {
}
this.LogUser = this.LogUser.bind(this);
this.LogoutUser = this.LogoutUser.bind(this);
}
LogUser(User, ID){
cookies.set('User', User, { path: '/' });
cookies.set('UserID', ID,{ path: '/'});
}
LogoutUser(){
cookies.remove('User')
}
render() {
return (
<div>
<div>
//MENU <- WHERE I CAN'T PASS THE PROPS OF USER AND USERID
<Menu render={(props) => <Menu {...props} User={cookies.get('User')} ID={cookies.get('UserID')} LogOutUser={this.LogoutUser} />}/>
</div>
<Router history = {history} >
<div>
//I have removed all other routes as they are not needed, but here is an example, in which the passing of props works
<Route path = "/Profile" render={(props) => <Profile {...props} User={cookies.get('User')} ID={cookies.get('UserID')} LogOutUser={this.LogoutUser} />}/>
</div>
</Router>
</div>
);
}
}
export default App;
And for example in Profile.jsx, I can do that:
PROFILE.JSX
export default class Profile extends Component {
constructor(props, context) {
super(props, context);
this.state = {
LoggedUser: '',
UserID: '',
};
this.LogOutClick = this.LogOutClick.bind(this);
}
LogOutClick(){
this.props.LogOutUser();
history.push('/Logout');
}
componentDidMount(){
if (this.props.User !== undefined)
{
this.setState({LoggedUser: this.props.User, UserID: this.props.ID})
}
else
{
history.push('/Login');
}
}
render() {
return (
<div>
Hello, {this.props.User}!
<div>
)}}
But when I try it in the Menu component, I can't manage it to update accordingly:
NAVBAR.JSX
export default class Menu extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
isOpen: false,
Title: '',
};
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
//here I tried to put something similar to the ComponentDidMount() in Profile.jsx, but it didn't work.
componentDidMount(){
if (this.props.User !== undefined)
{
this.setState({LoggedUser: this.props.User, UserID: this.props.ID})
this.setState({Title: "LOGGED IN"})
}
else
{
this.setState({Title: "LOGIN/REGISTER"})
}
}
render() {
console.log(this.state.User)
console.log(this.state.ID)
return (
<div>
<Navbar color="light" light expand="md">
<NavbarBrand href="/"><img src={require('./images/home.png')} width = "25px" height = "25px"/></NavbarBrand>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto1" navbar>
<NavItem>
<NavLink href="/Ads"><b>ADS</b></NavLink>
</NavItem>
<NavItem>
<NavLink href="/Profile"><b>YOUR PROFILE</b></NavLink>
</NavItem>
<NavItem>
//What I want to update
<NavLink href="/Login"><b>{this.state.Title}</b></NavLink>
</NavItem>
</Nav>
</Collapse>
</Navbar>
</div>
);
}
}
React will only update in response to a new state or new props. You are manipulating a cookie which can't cause a component re-render. Here's a solution:
In your App component change the Log methods to:
constructor(){
super();
this.state ={
currentUserId: cookies.get('UserID'),
currentUser: cookies.get('User')
};
this.LogUser = this.LogUser.bind(this);
this.LogoutUser = this.LogoutUser.bind(this);
}
LogUser(User, ID){
cookies.set('User', User, { path: '/' });
cookies.set('UserID', ID,{ path: '/'});
this.setState({
currentUserId: ID,
currentUser: User
});
}
LogoutUser(){
cookies.remove('User');
this.setState({
currentUserId: null,
currentUser: null
});
}
And your render will become:
render() {
return (
<div>
<div>
<Menu render={(props) => <Menu {...props} User={this.state.currentUser} ID={this.state.currentUserId} LogOutUser={this.LogoutUser} />}/>
</div>
<Router history = {history} >
<div>
//I have removed all other routes as they are not needed, but here is an example, in which the passing of props works
<Route path = "/Profile" render={(props) => <Profile {...props} User={this.state.currentUser} ID={this.state.currentUserId} LogOutUser={this.LogoutUser} />}/>
</div>
</Router>
</div>
);
}

React router error (Failed prop type: Invalid prop `children` supplied to `Switch`, expected a ReactNode.)

Try to modify component, the main idea is, I want create login page, try modify App.js but get error
warning.js?6327:36 Warning: Failed prop type: Invalid prop children
supplied to Switch, expected a ReactNode.
My code is:
class App extends Component {
constructor(props) {
super(props)
}
routeWithSubRoutes(route) {
return (
<Route
key={_.uniqueId()}
exact={route.exact || false}
path={route.path}
render={props => (
// Pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes || null} />
)}
/>
);
}
render () {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => this.routeWithSubRoutes.bind(this,route))}
</Switch>
</div>
)
}
}
export default App;
Code that I try to modify
export default () => {
// Use it when sub routes are added to any route it'll work
const login = () => {
}
const routeWithSubRoutes = route => (
<Route
key={_.uniqueId()}
exact={route.exact || false}
path={route.path}
render={props => (
// Pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes || null} />
)}
/>
);
var isLogin = false;
if(!isLogin) {
return (
<Login />
)
}
if(isLogin) {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => routeWithSubRoutes(route))}
</Switch>
</div>
);
}
};
this code is working, but my not, how to fix this?
Function.bind doesn't call the function, it only binds its context. Instead, you should bind it in the constructur:
class App extends Component {
constructor(props) {
super(props)
this.routeWithSubRoutes = this.routeWithSubRoutes.bind(this)
}
/* ... */
render () {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => this.routeWithSubRoutes(route))}
</Switch>
</div>
)
}
}

Categories

Resources