2 Props passed to Child Component with a Map Function - javascript

Hi how can I pass the Path into the map function! Have tried as nested array but can't get it
Navbar (parent component):
class Navbar extends Component {
constructor(props) {
super(props);
this.state = {
nav: [{
topic: 'Home',
path: '/',
},
{
topic: 'Bogen',
path: '/Bogen'
},
],
topics: ['Home', 'Bogen', 'Projekt', 'Nutzer'],
path: ['/', '/Bogen', '/Projekt', '/Nutzer'],
}
}
render() {
return (
<div className='navbar'>
<NavbarTopics topics={this.state.topics}
/>
</div>
)
}
}
NavbarTopics (child component):
const NavbarTopics = (props) => (
<ul className='ul_Ntopics'>
{props.topics.map((topic, index) => <NavTopic topic={topic} key={index} />)}
</ul>
)
NavTopic (child component):
const NavTopic = (props) => <li className='li_Ntopic'><Link className='Link_Ntopic' to=''>{props.topic}</Link></li>;
NavTopic.propTypes = {
topic: PropTypes.string.isRequired,
}
export default NavTopic;
How can I pass the state path to the map-function, so that I can pass it as prop to NavTopic?

Instead of passing topic as a prop, pass nav.
//Navbar
<NavbarTopics nav={this.state.nav}
Then iterate through nav array
// NavbarTopics
const NavbarTopics = (props) => (
<ul className='ul_Ntopics'>
{props.nav.map((nav, index) => <NavTopic nav={nav} key={index} />)}
</ul>
)
In NavTopic,
const NavTopic = (props) => <li className='li_Ntopic'>
<Link className='Link_Ntopic' to={props.nav.path}>{props.nav.topic}</Link>
</li>;

Related

Re-Render From Child Component After State Change

So what I want is, whenever I click on any list item the item should get struck. I've changed the state but I'm not sure about how to re-render this with new state. Can anyone please let me know how to do this?
Please don't mind my mistakes, I'm a newbie to this and also suggest me how to do this in better way if needed.
Child Components
class TodoList extends Component {
render() {
return (
<ul>
{this.props.items.map(thing => (
<List
key={thing.id}
item={thing}
items={this.props.items}
listid={thing.id}
/>
))}
</ul>
);
}
}
class List extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
render() {
return <li onClick={this.handleClick}>{this.props.item.item}</li>;
}
handleClick(event) {
const items = this.props.items;
items[this.props.listid] = {
item: "<strike>" + event.target.value + "</strike>",
id: this.props.listid
};
console.log(items);
this.setState({
items
});
}
}
You need to keep todos state in a parent component, and allow to state change from the inner components.
For example:
App.js (here we keep todos in state, and pass toggle function, and todo items as props to child components:
import React, { Component } from "react";
import TodoList from "./TodoList";
class App extends Component {
state = {
todos: [
{
id: 1,
text: "todo 1",
completed: false
},
{
id: 2,
text: "todo 2",
completed: true
},
{
id: 3,
text: "todo 3",
completed: true
}
]
};
toggle = id => {
const updatedTodos = this.state.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
};
}
return todo;
});
this.setState({
...this.state,
todos: updatedTodos
});
};
render() {
return <TodoList items={this.state.todos} toggle={this.toggle} />;
}
}
export default App;
TodoList.js
import React, { Component } from "react";
import Todo from "./Todo";
class TodoList extends Component {
render() {
return (
<ul>
{this.props.items.map(todo => (
<Todo key={todo.id} item={todo} toggle={this.props.toggle} />
))}
</ul>
);
}
}
export default TodoList;
Todo.js (here we call toggle function when handleClick, and pass the id of the todo)
import React, { Component } from "react";
class Todo extends Component {
handleClick = id => {
this.props.toggle(id);
};
render() {
const { id, text, completed } = this.props.item;
return (
<li onClick={() => this.handleClick(id)}>
{completed ? <strike>{text}</strike> : text}
</li>
);
}
}
export default Todo;
here is codesandbox:
https://codesandbox.io/s/polished-cloud-hmdw0

Why can't I add any value to the array in state?

I have a lot of hits, which I want to add to an array once a hit is pressed. However, as far as I observed, the array looked like it got the name of the hit, which is the value. The value was gone in like half second.
I have tried the methods like building constructor, and doing things like
onClick={e => this.handleSelect(e)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
defaultValue={hit.name}
and so on
export default class Tagsearch extends Component {
constructor(props) {
super(props);
this.state = {
dropDownOpen:false,
text:"",
tags:[]
};
this.handleRemoveItem = this.handleRemoveItem.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
}
handleSelect = (e) => {
this.setState(
{ tags:[...this.state.tags, e.target.value]
});
}
render() {
const HitComponent = ({ hit }) => {
return (
<div className="infos">
<button
className="d-inline-flex p-2"
onClick={e => this.handleSelect(e)}
value={hit.name}
>
<Highlight attribute="name" hit={hit} />
</button>
</div>
);
}
const MyHits = connectHits(({ hits }) => {
const hs = hits.map(hit => <HitComponent key={hit.objectID} hit={hit}/>);
return <div id="hits">{hs}</div>;
})
return (
<InstantSearch
appId="JZR96HCCHL"
apiKey="b6fb26478563473aa77c0930824eb913"
indexName="tags"
>
<CustomSearchBox />
{result}
</InstantSearch>
)
}
}
Basically, what I want is to pass the name of the hit component to handleSelect method once the corresponding button is pressed.
You can simply pass the hit.name value into the arrow function.
Full working code example (simple paste into codesandbox.io):
import React from "react";
import ReactDOM from "react-dom";
const HitComponent = ({ hit, handleSelect }) => {
return <button onClick={() => handleSelect(hit)}>{hit.name}</button>;
};
class Tagsearch extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: []
};
}
handleSelect = value => {
this.setState(prevState => {
return { tags: [...prevState.tags, value] };
});
};
render() {
const hitList = this.props.hitList;
return hitList.map(hit => (
<HitComponent key={hit.id} hit={hit} handleSelect={this.handleSelect} />
));
}
}
function App() {
return (
<div className="App">
<Tagsearch
hitList={[
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
]}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
additionally:
note the use of prevState! This is a best practice when modifying state. You can google as to why!
you should define the HitComponent component outside of the render method. it doesn't need to be redefined each time the component is rendered!

Re-Render in component on State Change in Redux Store

I wanted to show logoff button after user signs in and hide visibility of button after the user logged out. Menu items are coming from Menu.js file.
The current workflow of the app is, if user signed in, the auth state is updated in the store, based on that state, I wanted to show a logoff Button in Menu.
The auth state is correctly updated in Redux store on login and I can get the value of auth in Menu.js File from Redux store, but the change is not rendered in Siderbar.js until i refresh the page.
What should i need to do in sidebar.js to fetch the menu on auth state change in Store.
Siderbar.js
<pre><code>
import React, { Component } from 'react';
import { withNamespaces, Trans } from 'react-i18next';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link, withRouter } from 'react-router-dom';
import { Collapse, Badge } from 'reactstrap';
import SidebarRun from './Sidebar.run';
import SidebarUserBlock from './SidebarUserBlock';
import Menu from '../../Menu';
/** Component to display headings on sidebar */
const SidebarItemHeader = ({item}) => (
<li className="nav-heading">
<span><Trans i18nKey={item.translate}>{item.heading}</Trans></span>
</li>
)
/** Normal items for the sidebar */
const SidebarItem = ({item, isActive}) => (
<li className={ isActive ? 'active' : '' }>
<Link to={item.path} title={item.name}>
{item.label && <Badge tag="div" className="float-right" color={item.label.color}>{item.label.value}</Badge>}
{item.icon && <em className={item.icon}></em>}
<span><Trans i18nKey={item.translate}>{item.name}</Trans></span>
</Link>
</li>
)
/** Build a sub menu with items inside and attach collapse behavior */
const SidebarSubItem = ({item, isActive, handler, children, isOpen}) => (
<li className={ isActive ? 'active' : '' }>
<div className="nav-item" onClick={ handler }>
{item.label && <Badge tag="div" className="float-right" color={item.label.color}>{item.label.value}</Badge>}
{item.icon && <em className={item.icon}></em>}
<span><Trans i18nKey={item.translate}>{item.name}</Trans></span>
</div>
<Collapse isOpen={ isOpen }>
<ul id={item.path} className="sidebar-nav sidebar-subnav">
{ children }
</ul>
</Collapse>
</li>
)
/** Component used to display a header on menu when using collapsed/hover mode */
const SidebarSubHeader = ({item}) => (
<li className="sidebar-subnav-header">{item.name}</li>
)
class Sidebar extends Component {
state = {
collapse: {},
isLoggedIn: ''
}
componentDidMount() {
// pass navigator to access router api
SidebarRun(this.navigator.bind(this));
// prepare the flags to handle menu collapsed states
this.buildCollapseList()
}
/** prepare initial state of collapse menus. Doesnt allow same route names */
buildCollapseList = () => {
let collapse = {};
Menu
.filter(({heading}) => !heading)
.forEach(({name, path, submenu}) => {
collapse[name] = this.routeActive(submenu ? submenu.map(({path})=>path) : path)
})
this.setState({collapse});
}
navigator(route) {
this.props.history.push(route);
}
routeActive(paths) {
paths = Array.isArray(paths) ? paths : [paths];
return paths.some(p => this.props.location.pathname.indexOf(p) > -1)
}
toggleItemCollapse(stateName) {
for (let c in this.state.collapse) {
if (this.state.collapse[c] === true && c !== stateName)
this.setState({
collapse: {
[c]: false
}
});
}
this.setState({
collapse: {
[stateName]: !this.state.collapse[stateName]
}
});
}
getSubRoutes = item => item.submenu.map(({path}) => path)
/** map menu config to string to determine what element to render */
itemType = item => {
if (item){
// console.log(item)
if (item.heading) return 'heading';
if (!item.submenu) return 'menu';
if (item.submenu) return 'submenu';
}}
render() {
return (
<aside className='aside-container'>
{ /* START Sidebar (left) */ }
<div className="aside-inner">
<nav data-sidebar-anyclick-close="" className="sidebar">
{ /* START sidebar nav */ }
<ul className="sidebar-nav">
{ /* START user info */ }
<li className="has-user-block">
<SidebarUserBlock/>
</li>
{ /* END user info */ }
{ /* Iterates over all sidebar items */ }
{
Menu.map((item, i) => {
// heading
if(this.itemType(item) === 'heading')
return (
<SidebarItemHeader item={item} key={i} />
)
else {
if(this.itemType(item) === 'menu')
return (
<SidebarItem isActive={this.routeActive(item.path)} item={item} key={i} />
)
if(this.itemType(item) === 'submenu')
return [
<SidebarSubItem item={item} isOpen={this.state.collapse[item.name]} handler={ this.toggleItemCollapse.bind(this, item.name) } isActive={this.routeActive(this.getSubRoutes(item))} key={i}>
<SidebarSubHeader item={item} key={i}/>
{
item.submenu.map((subitem, i) =>
<SidebarItem key={i} item={subitem} isActive={this.routeActive(subitem.path)} />
)
}
</SidebarSubItem>
]
}
return null; // unrecognized item
})
}
</ul>
{ /* END sidebar nav */ }
</nav>
</div>
{ /* END Sidebar (left) */ }
</aside>
);
}
}
Sidebar.propTypes = {
isLoggedIn: PropTypes.object.isRequired
};
const mapStateToProps = (state, ownProps) => {
// console.log("Sidebar: ", state.auth);
return{
isLoggedIn: state.auth,
}}
export default connect(mapStateToProps)(withRouter(Sidebar));
What should i do in Sidebar.js, that it re-renders the menu on auth state change.
Any help would be highly appreciated.
Menu.js File
import configureStore from './store/store';
const store = configureStore();
function select(state) {
return state.auth
}
let loggedIn = select(store.getState());
let Meu = [
{
name: 'Analytics',
icon: 'icon-speedometer',
translate: 'sidebar.nav.DASHBOARD',
submenu: [{
name: 'Overview',
path: '/dashboard',
translate: 'sidebar.nav.element.HOME'
},
{
name: 'Revenue',
path: '/revenue',
translate: 'sidebar.nav.element.REVENUE'
},
{
name: 'Source',
path: '/source',
translate: 'sidebar.nav.element.SOURCE'
},
{
name: 'Marketing',
path: '/marketing',
translate: 'sidebar.nav.element.MARKETING'
}
]
},
{
name: 'Tools',
icon: 'fa fa-briefcase',
translate: 'sidebar.nav.TOOLS',
submenu: [
{
name: 'Customer Service',
path: '/customer-service',
translate: 'sidebar.nav.element.CUSTOMER-SERVICE'
}
]
},
{
name: 'Settings',
icon: 'icon-settings',
translate: 'sidebar.nav.SETTINGS',
submenu: [{
name: 'Profile Settings',
path: '/profile',
translate: 'sidebar.nav.element.PROFILE-SETTINGS'
},
{
name: 'Company Settings',
path: '/company-settings',
translate: 'sidebar.nav.element.SETTINGS'
}
]
},
{(loggedIn.authResponse) ?
({
name: 'Logoff',
icon: 'icon-lock',
path: '/logoff'
}) : null
}
];
const Menu = Meu.filter(word => word !== null)
export default (Menu)
Inside the parent component of your log out button you can conditionally render the log out button; take note this means your parent component is connected to redux via connect(mapStateToProps)
{ this.props.isLoggedIn ? <Button>Log Out</Button> : null }

calling grandparent method from grandchild functional component in react

I'm trying to call a simple method from the grandparent component in my child component but from some reason I can't , I tried every possible way but I think I'm missing something
here's the full code :
import React, { Component } from 'react';
import './App.css';
var todos = [
{
title: "Example2",
completed: true
}
]
const TodoItem = (props) => {
return (
<li
className={props.completed ? "completed" : "uncompleted"}
key={props.index} onClick={props.handleChangeStatus}
>
{props.title}
</li>
);
}
class TodoList extends Component {
constructor(props) {
super(props);
}
render () {
return (
<ul>
{this.props.todosItems.map((item , index) => (
<TodoItem key={index} {...item} {...this.props} handleChangeStatus={this.props.handleChangeStatus} />
))}
</ul>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos ,
text :""
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeStatus = this.handleChangeStatus(this);
}
handleTextChange(e) {
this.setState({
text: e.target.value
});
}
handleChangeStatus(){
console.log("hello");
}
handleSubmit(e) {
e.preventDefault();
const newItem = {
title : this.state.text ,
completed : false
}
this.setState((prevState) => ({
todos : prevState.todos.concat(newItem),
text : ""
}))
}
render() {
return (
<div className="App">
<h1>Todos </h1>
<div>
<form onSubmit={this.handleSubmit}>
< input type="text" onChange={this.handleTextChange} value={this.state.text}/>
</form>
</div>
<div>
<TodoList handleChangeStatus={this.handleChangeStatus} todosItems={this.state.todos} />
</div>
<button type="button">asdsadas</button>
</div>
);
}
}
export default App;
The method im trying to use is handleChangeStatus() from the App component in the TodoItem component
Thank you all for your help
This line is wrong:
this.handleChangeStatus = this.handleChangeStatus(this);
//Change to this and it works
this.handleChangeStatus = this.handleChangeStatus.bind(this);

How to pass a prop to a component that is returned based on state?

I am attempting to return a component based on a parameter that was passed on a onClick handler this.showComponentToRender('features'). If features is clicked, it runs the showComponentToRender(name) and sets the state and in the render(), the {this.state.showComponent} shows the proper component.
However, a problem surfaces when I attempt to pass a prop resetFeatures={this.props.resetFeatures} within the
showFeatures() {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
/>);
}
Clicking on the RESET A4 link, calls the resetCrossbow() which activates a function in the parent component. The parent component updates its state, and passes the state as a prop to its child.
For some reason, I can not get the resetFeatures prop to come into the <FeaturesList /> component if I return it within a function that gets set in state. Why is this? I am looking for suggestions to fix.
If I do the traditional method of placing the <FeaturesList /> within the return of the render(), all is well.
Here's the component
import React, { Component } from 'react';
import FeaturesList from './FeaturesList';
import ColorsList from './ColorsList';
import './../assets/css/features-menu.css';
export default class FeaturesMenu extends Component {
constructor(props) {
super(props);
this.state = {
showComponent: this.showFeatures()
};
this.showFeatures = this.showFeatures.bind(this);
this.showColors = this.showColors.bind(this);
}
showFeatures() {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
/>);
}
showColors() {
this.props.resetCrossbow();
return <ColorsList switchColor={this.props.switchColor} />
}
showComponentToRender(name) {
if (name === 'features') {
this.setState({
showComponent: this.showFeatures()
});
} else {
this.setState({
showComponent: this.showColors()
})
}
}
render() {
// console.log(`this.props.resetFeatures: ${this.props.resetFeatures}`);
return (
<div id="features-menu-wrapper">
<nav id="features-menu">
<li onClick={() => this.showComponentToRender('features')}>FEATURES</li>
<li onClick={() => this.showComponentToRender('colors')}>COLORS</li>
<li onClick={() => this.props.resetCrossbow()}>RESET A4</li>
</nav>
<div id="component-wrapper">
{this.state.showComponent} // <- I am not able to pass resetFeatures prop if I do it this way. Why?
{/* <FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures} <- I am able to pass resetFeatures prop as normal.
/>
<ColorsList switchColor={this.props.switchColor} /> */}
</div>
</div>
);
}
}
The easiest way to achieve results is pass additional properties to render specific components function and use spread to pass these props to the rendered component:
const FeaturesList = ({additional = 'Empty'} = {}) => <div>Feature List with additional prop <b>{additional}</b></div>
const ColorsList = ({additional = 'Empty'} = {}) => <div>Colors List with additional prop <b>{additional}</b></div>
class FeaturesMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
showComponent: this.showFeatures({additional: 'Initial features'})
};
this.showFeatures = this.showFeatures.bind(this);
this.showColors = this.showColors.bind(this);
}
showFeatures(props) {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
{...props}
/>);
}
showColors(props) {
this.props.resetCrossbow();
return <ColorsList switchColor={this.props.switchColor} {...props} />
}
showComponentToRender(name) {
if (name === 'features') {
this.setState({
showComponent: this.showFeatures({additional: 'features adds'})
});
} else {
this.setState({
showComponent: this.showColors({additional: 'colors adds'})
})
}
}
render() {
return (
<div id="features-menu-wrapper">
<nav id="features-menu">
<li onClick={() => this.showComponentToRender('features')}>FEATURES</li>
<li onClick={() => this.showComponentToRender('colors')}>COLORS</li>
<li onClick={() => this.props.resetCrossbow()}>RESET A4</li>
</nav>
<div id="component-wrapper">
{this.state.showComponent}
</div>
</div>
);
}
}
const props = {
resetCrossbow: () => null,
switchColor: () => null,
updateCad: () => null,
resetFeatures: () => null
}
ReactDOM.render(<FeaturesMenu {...props}/>, document.querySelector('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>
<root/>
You should not save the entire component in your state. Just set a string related to it and as the value changes, the FeaturesMenu will react and
call the function to render the correct component. Obviously, this code can be changed, but I think I've made my point. =)
const FeaturesList = props => (<div>Features List</div>);
const ColorsList = props => (<div>Colors List</div>);
class FeaturesMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
currentComponent: 'Features'
};
this.showFeatures = this.showFeatures.bind(this);
this.showColors = this.showColors.bind(this);
}
showFeatures() {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
/>);
}
showColors() {
this.props.resetCrossbow();
return <ColorsList switchColor={this.props.switchColor} />
}
showComponentToRender(name) {
this.setState({ currentComponent: name })
}
render() {
return (
<div id="features-menu-wrapper">
<nav id="features-menu">
<li onClick={() => this.showComponentToRender('Features')}>FEATURES</li>
<li onClick={() => this.showComponentToRender('Colors')}>COLORS</li>
<li onClick={() => this.props.resetCrossbow()}>RESET A4</li>
</nav>
<div id="component-wrapper">
{this[`show${this.state.currentComponent}`]()}
</div>
</div>
);
}
}
// for testing purposes
const props = {
resetCrossbow: () => {},
switchColor: () => {},
updateCad: () => {},
resetFeatures: () => {}
}
ReactDOM.render(<FeaturesMenu {...props}/>, document.querySelector('main'))
<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>
<main/>

Categories

Resources