ReactJS. calling a function from an outside variable - javascript

I'm new to ReactJS and trying to make a small web application.
I have a list of items to put in a sidebar, and I want each item to give back a status tu the sidebar when clicked (so that I can style the active link accordingly).
import React, {Component} from 'react';
import SideBarItem from "./SideBarItem";
const items = {
'DASHBOARD' : 'home',
'Utenti': 'user',
'Corsi' : 'education',
'Logistica' : 'check',
'Comunicazioni': 'bullhorn'
};
const listItems = Object.entries(items).map(([key,value])=>{
return <SideBarItem
onClick={this.changeState(key)} active={this.state.active == key ? 'active' : ''}
title={key}
glyph={'glyphicon glyphicon-' + value.toString()}/>
});
class SideBar extends Component {
constructor(props) {
super(props);
this.state = {active: 'DASHBOARD'};
}
changeState (row) {
this.setState({
active: row
});
}
render() {
return (
<div id = "sidebar" className="col-sm-3 col-md-2 sidebar paper-depth-1">
<ul className = 'nav nav-sidebar'>
{listItems}
</ul>
</div>
);
}
}
export default SideBar;
But this code is returnig the following error:
TypeError: _this.changeState is not a function
I understand that there's something wrong in calling a component function from an outside variable, but I don't get how can I make this work in any other way.

If you create the list of items in render(), the this scope will be the component instance, as you need it to be.
class SideBar extends Component {
constructor(props) {
super(props);
this.state = {active: 'DASHBOARD'};
}
changeState(row) {
this.setState({
active: row
});
}
render() {
return (
<div id="sidebar" className="col-sm-3 col-md-2 sidebar paper-depth-1">
<ul className="nav nav-sidebar">
{Object.entries(items).map(([key,value]) =>
<SideBarItem
onClick={() => this.changeState(key)}
active={this.state.active == key ? 'active' : ''}
title={key}
glyph={'glyphicon glyphicon-' + value.toString()}
/>
)}
</ul>
</div>
);
}
}

Related

How to identify a single div element among set of elements in react?

I have a react app with a list of Div elements to create some Cards. Each card has 'read more' button to expand and collapse a paragraph and I toggle it for each mouse click. My problem is, for each click, it expand paragraphs in all cards instead only paragraph in the card I clicked. So I can't identify the clicked (this) card.
Component:
class BidCard extends Component {
constructor(props) {
super(props);
this.state = {
readMoreOpen: false,
}
}
readMore() {
this.setState({ readMoreOpen: !this.state.readMoreOpen })
}
render() {
const { articles } = this.props;
return (
articles.map(article => {
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={this.state.readMoreOpen ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={this.readMore.bind(this)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
export default BidCard;
How can I solve this?
You can save id of the expanded card to the state and the check it when rendering items:
class BidCard extends Component {
constructor(props) {
super(props);
this.state = {
readMoreOpen: [], // Use array here
}
}
// Add card id to the expanded array if not already there, otherwise remove it
readMore = (id) => {
this.setState(state => {
if (state.readMoreOpen.includes(id)) {
return {readMoreOpen: state.readMoreOpen.filter(readId => readId !== id)}
}
return {readMoreOpen: [...state.readMoreOpen, id]}
})
}
render() {
const { articles } = this.props;
return (
articles.map(article => {
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
{/*Check if the item is in expanded items array */}
<p className={this.state.readMoreOpen.includes(article.id) ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={() => this.readMore(article.id)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
You will need to keep expanded state per every card.
I would recommend to create component for card
articles.map(article => {
return (
<Article key={article.id} {...article} />
)
})
)
class Article extends Component {
state = {
readMoreOpen: false
}
readMore() {
this.setState(state => ({ readMoreOpen: !state.readMoreOpen }))
}
render () {
const {description} = this.props;
return (<div className="projectCardRoot" >
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={this.state.readMoreOpen ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{description}</p>
<div className="cardReadMore desktopDiv" onClick={this.readMore.bind(this)}>Read more</div>
</div>
</div>
</div>)
}
}
Other approach is to keep array of booleans with information of which article div should be currently expanded in this method you will need to update state with id of expanded article
readMore(id) {
this.setState({ articles: this.props.articles.map(article => article.id === id ? true : false) } )
}
and in render use boolean from state as information if it should be expanded
That's because all your cards currently share the same source of truth. You used a ternary operator to determine what class a Card would have depending on the state-value. Well, all Cards are using the same state-value to compare, so understandably, if one is affected, then all would be too.
There's more than one way to resolve this, but the most appropriate would probably be to create a separate Card Component. This makes it so each Card component has their own state to keep track of.
See working sandbox: https://codesandbox.io/s/quizzical-mahavira-wz8iu
Parent.js
import React from "react";
import ReactDOM from "react-dom";
import Card from "./Card";
import "./styles.css";
class BidCard extends React.Component {
render() {
const { articles } = this.props;
return articles.map(article => {
return <Card article={article} />;
});
}
}
BidCard.defaultProps = {
articles: [{ description: "woof" }, { description: "meow" }]
};
const rootElement = document.getElementById("root");
ReactDOM.render(<BidCard />, rootElement);
Card.js
import React, { useState } from "react";
const Card = ({ article }) => {
const [readOpen, setReadOpen] = useState(false);
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p
className={readOpen ? "openFullParagraph" : "closeFullParagraph"}
id="projectCardDesc"
>
{article.description}
</p>
<div
className="cardReadMore desktopDiv"
onClick={() => setReadOpen(!readOpen)}
>
Read more
</div>
</div>
</div>
</div>
);
};
export default Card;
I did a few modifications to your code. This way it should work.
I added comments that explain the the changes. The main idea is that you should not simply store the boolean readMoreOpen status (which in your code is treated as a kind of shared between all the cards) but specific card identity.
My changes works if there could be only one "expanded" card at any moment. If your design supposes that there could be a few "expanded" cards at the same time the solution would be more complex though not much.
class BidCard extends Component {
constructor(props) {
super(props);
// the way you've tried to keep status (open/closed) it wasn't tied to any speciifc card
// you should store this specific card instead
this.state = {
//readMoreOpen: false,
expandedCard: null,
}
this.readMore = this.readMore.bind(this);
}
readMore(article) {
//this.setState({ readMoreOpen: !this.state.readMoreOpen })
this.setState({expandedCard: article})
}
render() {
const { articles } = this.props;
const { expandedCard } = this.state;
return (
articles.map(article => {
// the look of each card depends on state.expandedCard only if article == expandedCard it's shown with 'openFullParagraph' class
return (
<div className="projectCardRoot" key={article.id}>
<div className="projectCardMainLogin">
<div className="projectCardMiddle">
<p className={article == expandedCard ? 'openFullParagraph' : 'closeFullParagraph'} id="projectCardDesc">{article.description}</p>
<div className="cardReadMore desktopDiv" onClick={() => this.readMore(article)}>Read more</div>
</div>
</div>
</div>
)
})
)
}
}
export default BidCard;

ReactJS Sending selection back up to parent

I know we send data into child components through props, but how does one send data back up to the parent?
I have a a series of dropdown boxes where an item can be selected:
DropdownMenu.js
import React, {Component} from 'react';
export default class DropdownMenu extends Component {
constructor(props) {
super(props);
this.state = {
highlight: false,
count: this.props.count || 0,
selection: null
}
this.showDropdown = this.showDropdown.bind(this);
this.selectItem = this.selectItem.bind(this);
}
componentDidMount() {
}
showDropdown() {
}
selectItem(e) {
}
render() {
return <div className="dropdown__menu" onClick={this.props.onClick}>
{this.props.text} {this.state.count > 0 ? <b>{this.state.count}</b> : ''}
<div className="dropdown__content"
style={this.props.isOpen ? {'display': 'block'} : {'display': 'none'}}>
{this.props.children}
</div>
</div>
}
}
Which is called from CompanyList.js
constructor(props) {
super(props);
this.state = {
sortBy: null,
...
<DropdownMenu text="Sort by" isOpen={this.state.activeDropdown === "Sort_by"}
onClick={this.showDropdown.bind(this, "Sort_by")}
onMouseEnter={() => this.setState({mouseInDropdown: true})}
onMouseLeave={() => this.setState({mouseInDropdown: false})}>
<div onMouseEnter={() => this.setState({mouseInDropdown: true})}
onMouseLeave={() => this.setState({mouseInDropdown: false})}>
<li>Name</li>
<li>Age</li>
<li>Value</li>
</div>
</DropdownMenu>
...
Thanks for the replies but how do I apply the function to each of the <li> that are children of the dropdown (some dropdowns may not have any options and are just buttons)?
this.props.children
TL;DR --> https://reactjs.org/docs/faq-functions.html
Just pass a callback from the parent to the child :
// parent component
(...)
myCallback(arg1){
this.setState({val:arg1});
}
render(){
return(
<MyChildComponent onSpecificAction={this.myCallback.bind(this)} />
);
}
(...)
// child component
(...)
render(){
return(
<button onclick={(e) => this.props.onSpecificAction('value to send up')}>Button text</button>
);
}
(...)
In the same way that you are passing props to a child element, you can also pass functions.
So you can pass a function like onChange(newData) from the parent to your child component that you will call everytime the value changes.

React hidden navigation menu

I am working with ReactJS at the moment (I am very much a beginner). What I wanting to do is show/hide a menu component when a link is clicked in the site navigation. The menu is being built as a component that sits within a component (header).
The user clicks the menu button and that then toggles the menu to be shown or hidden, I am however having problems working out where the logic to show and hide the menu component should live the showing an hiding is relatively simple I basically want to add and remove a class to the menu component to show or hide it.
I have a similar show/hide working for my login and register forms, but the show and hide classes for these are added in the header component not the child component, here is my header component JS so far.
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Menu from './menu';
import LoginForm from '../Login';
import RegisterForm from '../Register';
export default class Explore extends Component {
constructor(props) {
super(props);
this.state = {
loginMenuVisible : false,
registerMenuVisible : false,
mainMenuVisible : false
};
console.log(this.state);
//this.triggerMenu = this.triggerMenu.bind(this);
}
render() {
return (
<div className="site__navigation">
<Menu />
<header className="site__header">
<img src="img/logo-full-color.png" alt="meatFree" />
<ul className="header__navigation">
<li className="header__navigation__item">
<a href="/register" onClick={this.toggleMenu.bind(this, 'register')}>Register</a>
<ul className={this.state.registerMenuVisible ? "dropdown visible" : "dropdown"}>
<li>
<LoginForm />
</li>
</ul>
</li>
<li className="header__navigation__item">
<a href="#" onClick={this.toggleMenu.bind(this, 'login')}>Login</a>
<ul className={this.state.loginMenuVisible ? "dropdown visible" : "dropdown"}>
<li>
<LoginForm />
</li>
</ul>
</li>
<li className="header__navigation__item">
<a href="" className="nav__toggle">
Menu
</a>
</li>
</ul>
</header>
</div>
);
}
toggleMenu(type, e) {
e.preventDefault();
console.log(type);
switch(type) {
case 'login':
if(this.state.loginMenuVisible) {
this.setState({loginMenuVisible : false});
} else {
this.setState({
registerMenuVisible : false,
loginMenuVisible : true
});
}
break;
case 'register':
if(this.state.registerMenuVisible) {
this.setState({registerMenu : false});
} else {
this.setState({
registerMenuVisible : true
});
}
break;
case 'menu':
this.setState({mainMenuVisible : true });
}
}
}
AS you see I have a element in the state called mainMenuVisible, I want to be able to maniuplate this within the Menu component.
I like to keep things simple:
export default class Example extends Component {
constructor(){
super(props)
this.state = {
showMenu: false
}
this.menuShowToggle = this.menuShowToggle.bind(this);
}
menuShowToggle = () => {
this.setState({showMenu: !this.state.showMenu})
}
render = () => {
return (
<div>
<div className={this.state.showMenu ? 'showMenu': 'hideMenu'}>Hidden Menu</div>
<button type="button" onClick={this.menuShowToggle} value="toggle" />
</div>
)
}
}
To achieve this using your current setup, you could pass the toggleMenu() function as a prop to your rendered Menu component
<Menu toggleMenu={this.toggleMenu} />
Then within the Menu component, you could call toggleMenu() in a click handler and pass in 'menu' as the type.
If you are using ReactJs you should consider the following pattern:
1. Set a state specifically to show/hide the the menu
2. Create a function which will toggle the state
3. Add a conditional rendering for the component menu
Here is how the the code can be done:
Step 1.
constructor(props) {
super(props);
this.state = {
isComponenetMenuVisisble : false, // by default to disable it
};
}
Step 2.
toggleComponentMenu = () => {
const isComponenetMenuVisisble = !this.state.isComponenetMenuVisisble;
this.setState({ isComponenetMenuVisisble });
}
Step 3.
{this.state.isComponenetMenuVisisble && <Menu />}
// if isComponenetMenuVisisble is true render <Menu />
And then you can add the event handler like the following
<div className="your-element" onClick={this.toggleComponentMenu}>
... your stuff
</div>
A good point to highlight in the code above is that I am using a method rather than a function in step 2. When using a method instead of a function there will be no need to bind the function on event handlers because this context will be the class itself. Much more details can be found about this in here http://blog.andrewray.me/react-es6-autobinding-and-createclass/

React - How to show relative div when mouse hover on a html tag?

Below is my code...
<ul className="no-style board__list">
{Object.keys(today.books).map(function(id) {
var refBook = today.books[id][0];
return (
<li key={refBook._id} className="board__list-item">
<div className="container flexrow">
<div className="flexrow__fit-2">{refBook.book_no}</div>
<div className="flexrow__org">
<span className="board__icon-wrap">
{refBook.memo
? (<i className="fa fa-flag" style={{color:"#F9AB9F"}}></i>)
: null
}
</span>
{refBooking.memo
? (<div className="memo_dialog">{refBook.memo}</div>)
: null
}
</div>
</div>
</li>
);
})}
</ul>
I have a object books array and I create a fa-flag icon for each book.
What I want is to show different memo dialog when mouse hover on each flag icon.
I know how to do it with query but how can I do this in react way not using jquery?
I'm not sure what are you trying to achieve but this example might be useful for you
class Book extends React.Component {
constructor(props){
super(props);
this.handleOver = this.handleOver.bind(this);
}
handleOver(name){
this.props.over(this.props.name)
}
render(){
return <div onMouseOver={this.handleOver}>{this.props.name}</div>
}
}
class BookList extends React.Component {
constructor(props){
super(props);
this.mouseOver = this.mouseOver.bind(this);
this.state = {
books: ['hello', 'amazing', 'world'],
memo: ''
}
}
mouseOver(name){
this.setState({memo: name})
}
render(){
const bookList = this.state.books.map((book, index)=>{
return <Book key={index} name={book} over={this.mouseOver}/>
});
return <div>
{bookList}
<hr/>
<div>{this.state.memo}</div>
</div>
}
}
React.render(<BookList />, document.getElementById('container'));
Also fiddle example.
I hope it will help you. Thanks
I suggest you to use isHovered state variable, to store hover state.
We are displaying some component(in your case it would be dialog box), if isHovered is true and hide it when this variable is false.
When we will hover on link element, we will trigger handleEnter function to set isHovered variable to true.
Similarly, when we are moving cursor out of link element, we are triggering handleLeave function to set isHovered variable to false.
Example:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
isHovered: false,
};
}
handleEnter() {
this.setState({
isHovered: true
});
}
handleLeave() {
this.setState({
isHovered: false
});
}
render() {
return (
<div>
<a
onMouseEnter={this.handleEnter.bind(this)}
onMouseLeave={this.handleLeave.bind(this)}
>Link</a>
{this.state.isHovered ? (
<div className="box">A component</div>
) : (
<div />
)}
</div>
);
}
}
Also, you can see demo at CodePen.

Toggle active class on child components

I'm having a bit of a head ache trying to figure out the React way of implementing this.
I have a Searches component which houses SearchItems, when an item is clicked among other things I need to set it's state to active to that it gets the correct CSS, I managed to get this working fine but how would I go about removing the active state from the others?
I was thinking that I could pass down a function from the top level component that would take the ID of the search, when clicked it'd zip through SearchItems and change their state to either true/false depending on which ID it was?
Code below!
Top level component:
import React from "react";
import {Link} from "react-router";
import Search from "./Search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
]
};
}
render() {
const { searches } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
Search items component
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
this.setState({selected: true});
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}
I think you don't need the state in the child component at all. In fact is a good idea to avoid having state in most components so they are easy to reason and reuse.
I would leave all the state only on the parent component in this case.
TOP Component:
import React from "react";
import Search from "./search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : null
};
}
_onSearchSelect(searchId) {
this.setState({'activeElement': searchId})
}
render() {
const { searches, activeSearchId } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}
isActive={search.id === activeElement}
onSelect={this._onSearchSelect.bind(this)} />
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD Component:
import React from "react";
export default class Search extends React.Component {
_getPanelClassNames() {
const { isActive } = this.props
return 'row panel panel-success ' + (isActive ? 'active' : 'default')
}
_onSelect() {
const { id, onSelect } = this.props;
onSelect(id)
}
render() {
const { searchName, matches } = this.props;
const panelStyle = { height: '90px', marginBottom: '6px', boxShadow: '' }
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={panelStyle} className={this._getPanelClassNames()}>
<div className="col-xs-4">
Search Name: {searchName}
</div>
<div className="col-xs-3">
Must Have: PHP, MySQL
</div>
<div className="col-xs-3">
Could Have: AngularJS
</div>
<div className="col-xs-2">
<button type="button" onClick={this._onSelect.bind(this)}
style={buttonStyle} className="btn btn-default btn-lg"
>
{matches}
</button>
</div>
</div>
);
}
}
You can also see it running in Plunker: https://plnkr.co/edit/sdWzFedsdFx4MpbOuPJD?p=preview
Ok it turns out this is simpler than I thought and is simply a case of understanding how react works(and not getting confused) .
When you have a top level component you pass it's state via props to children, when you update the state in the top level component it'll pass that down to the children and you can use componentWillReceiveProps to take action.
I added a function to my top level component called updateActiveSearch which simply sets the state of the TOP level component I then passed the activeElement state as a prop to the child Elements along with the function. When a child element calls this function to set itself as active all of them will fire componentWillReceiveProps, they simply just need to check their own ID against the one they've received, if it matches they're active, if it doesn't they're not!
So my top level component now looks like this:
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : 0
};
}
// This function gets passed via a prop below
updateActiveSearch(id){
//console.log(id);
this.setState({activeElement : id});
}
render() {
const SearchItems = this.state.searches.map((search) => {
return <Search activeElement={this.state.activeElement} goFunction={this.updateActiveSearch.bind(this)} key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD COMPONENTS
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
// This happens right before the props get updated!
componentWillReceiveProps(incomingProps){
if(incomingProps.activeElement == this.props.id){
this.setState({selected: true});
} else {
this.setState({selected: false});
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
//this.state.panelStyle.boxShadow = '-2px 3px 20px 5px rgba(255,198,0,1)';
this.setState({selected: true});
this.props.goFunction(this.props.id);
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}

Categories

Resources