React/Redux - Events between parent and childs for menu and items - reactjs

I'm trying to build a Menu, with MenuItem(s), from scratch. I'm using React 0.14 and Redux, with CoffeeScript (cjsx). (I'm just getting started with all of those)
I'm stuck because I don't know how to give the MenuItem(s) the reference to the callback function onChildrenClick. Because they are generated through {this.props.children} I don't know how I'm supposed to add a props there.
Menu.cjsx
React = require 'react'
class Menu extends React.Component
#propTypes =
children: React.PropTypes.array.isRequired
render: ->
<div className="ui pointing menu">
{this.props.children} # I don't see how I can bind the "onChildrenClick" function to the child, because they're generated outside of this scope. Maybe by looping on "this.props.children"? I tried but it wasn't successful.
</div>
onChildrenClick: (event) ->
console.log event
module.exports = Menu
MenuItem.cjsx
React = require 'react'
classNames = require('classnames')
class MenuItem extends React.Component
#propTypes =
label: React.PropTypes.string.isRequired
active: React.PropTypes.bool
constructor: (props) ->
super props
#state =
classes: classNames(
'ui'
'item'
'active': this.props.active
)
render: ->
<a className={#state.classes} onClick={#onClick}>
{ this.props.label }
</a>
onClick: =>
console.log Object.assign #state.classes, {active: !#state.classes.active}
#setState({classes: Object.assign #state.classes, {active: true}})
console.log #state.classes # I need to notify the parent so it can notify the current active children to get disabled.
module.exports = MenuItem
Here is how I build my menu and its items:
<Menu>
<MenuItem active={true} label="Menu 1"></MenuItem>
<MenuItem label="Menu 2"></MenuItem>
<MenuItem label="Menu 3"></MenuItem>
</Menu>
In the example, I manually generate "Menu 1 ... 3" but they would actually be generated dynamically.
I don't know if I got the right approach here. Maybe I should use Redux for this? Eventually, I should use Routes, because each MenuItem should have its own url (Angular-like, with #menu-1, for instance)
I also wonder what would be the best approach to give a MenuItem a content. Thanks for insight.

This could be a candidate for React.Children, whereby you're able to specify props to be passed down to any abritrary child element - in this case onClick:
Menu.jsx
<div className="ui pointing menu">
{
React.Children.map(this.props.children,
function(child) {
return React.cloneElement(child, {
onClick: this.onClick
});
}.bind(this)
)
}
</div>
But I might suggest an alternative approach, whereby you pass the list items to the Menu component itself, which can then render each list item individually, rather than just as this.props.children:
Menu.jsx (Alt)
render() {
return (
<div className="ui pointing menu">
{
this.props.menuItems.map((item, index) => (
<MenuItem item={item} onClick={(e) => this.onClick(e, item)} }/>
)
}
</div>
)
}

I'd recommend passing the onClick function as a prop of MenuItem from wherever you're rendering the Menu.
E.g.
menu_wrapper.js
// This is a functional component because it is 'dumb' - does not have state
React = require('react')
Menu = require('menu')
MenuItem = require('menu_item')
menuWrapper = (props)=>
onMenuItemClick = (e)=>
console.log(e)
menuItems = ()=>
props.list.map(function(item) {
isActive = (item.label == props.activeItem)
<MenuItem onClick={onMenuItemClick} active={isActive} label={item.label}/>
}
<div id="menu-wrapper">
<Menu>
{menuItems()}
</Menu>
</div>
menu.js
// Also a 'dumb' component - component without state
React = require('react')
Menu = (props)=>
<div className="ui pointing menu">
{props.children}
</div>
module.exports = Menu
menu_item.js
React = require 'react'
classNames = require('classnames')
class MenuItem extends React.Component
#propTypes =
label: React.PropTypes.string.isRequired
active: React.PropTypes.bool
onClick: React.PropTypes.func.isRequired
constructor: (props) ->
super props
#state =
classes: classNames(
'ui'
'item'
'active': this.props.active
)
render: ->
<a className={#state.classes} onClick={#onClick}>
{ this.props.label }
</a>
onClick: (e)=>
#props.onClick(e)
console.log Object.assign #state.classes, {active: !#state.classes.active}
#setState({classes: Object.assign #state.classes, {active: true}})
console.log #state.classes # I need to notify the parent so it can notify the current active children to get disabled.
module.exports = MenuItem
Lastly, consider moving from CoffeeScript to ES6. Most everything you get in CoffeeScript is now available in ES6, and some. That said, I do miss implicit returns.

Related

How to check if all instances of a React Component have the same value for their state?

So right now, in my App file I have this:
{items.map(el => (
<Item
prop1={foo}
prop2={bar}
el={baz}
/>
))}
And in the <Item> component, I have this:
<span className={finishedClass ? "finishedItem" : ""}>
{props.el}
</span>
where finishedClass is a state variable.
So my question is, how can I check if finishedClass is true for every single <Item> component that gets generated as a result of the items.map?
So, you basically want to know if all the finishedClass state values in all the Item components are true. This can be simplified in the sense that if any of the Item components have finishedClass as false, you will perform a certain action.
So, what you can do is, pass a function as a prop to the Item component as follows:
{items.map(el => (
<Item
prop1={foo}
prop2={bar}
el={baz}
setFinishedClassToFalse={()=>{/*your statements*/}}
/>
))}
This function setFinishedClassToFalse will be called by the Item component if and only if its state value finishedClass is false. Obviously, there's a little more implementation to this than what I have described. But this should give you a start.
Parent component can communication with child component with parent props that have function in it. You can see code below onFinished property in Parent component has handleFinished() function value in it. That fucntion will be a bridge to help Child component to communicate with Parent component. On the Child component you must run onFinished props to triggering handleFinished function on Parent comoponent. In code below the trigger for props.onFinished is when <span> clicked by user.
import React from "react";
import ReactDOM from "react-dom";
import { Grid } from "react-flexbox-grid";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data: "test" };
}
render() {
const items = ["red", "green", "blue"];
const handleFinished = (e, index) => {
this.setState({ data: e });
console.log(this.state);
};
return (
<Grid>
{items.map((el, index) => (
<Child
prop1={"test"}
prop2={"test1"}
el={el}
onFinished={(e) => handleFinished(e, index)}
/>
))}
<div>{this.state.data.toString()}</div>
</Grid>
);
}
}
const Child = (props) => {
// example for your finishedClass value state
const finishedClass = true;
return (
<span
onClick={() => props.onFinished(finishedClass)}
className={finishedClass ? "finishedItem" : ""}
>
{props.el}{" "}
</span>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
As finishedClass is a state variable, if you just console.log(finishedClass) inside component it will just do the work

How to pass state from parent to child in react?

How do I pass a state attribute from parent to child? In the following implementation, the Dropdown component has a state "isActive" and I want to access it in the Button component to attach propper styling to it. The Dropdown has to generic as it is supposed to take different sorts of buttons.
<Dropdown items="...">
<Button active ="false" />
</Dropdown>
Dropdwon.js
...
constructor(props){
super(props)
this.state = {
isActive: true,
}
}
render (){
return (
<div className={styles.toggle} onClick={(event) => this.showMenu(event)}>
{this.props.children} /* want to set active prop for the child button here */
</div>
);
}
...
You have two possibilities:
Lift your Dropdown state and keep it in its parent component;
Use useContext hook;
The first approach would be better, but it may not be good for your application (I cannot know that). Let me make an example for both cases.
This is an example where I've lifted the isActive state to the parent component.
const ParentComponent = () => {
const [isActive, setIsActive] = useState(false);
handleIsActiveChange = (newValue) => {
setIsActive(newValue);
}
<Dropdown isActive={isActive} setIsActive={handleIsActiveChange}>
<Button isActive={isActive} />
</Dropdown>
}
const Dropdown = props => {
// You can use `props.isActive` to know whether the dropdown is active or not.
// You can use `props.handleIsActiveChange` to update the `isActive` state.
}
const Button = props => {
// You can use `props.isActive` to know whether the dropdown is active or not.
}
Instead, this exploits the useContext API:
const dropdownContext = React.createContext(null);
const Dropdown = props => {
const [isActive, setIsActive] = useState(false);
return (
<dropdownContext.Provider value={{ isActive }}>
{props.children}
</dropdownContext.Provider>
);
}
const Button = props => {
const dropdownCtx = React.useContext(dropdownContext);
// You can use `dropdownCtx.isActive` to know whether the dropdown is active or not.
}
Aside from the answer I linked, there might be another way of achieving this that I didn't see mentioned.
You can send a function as a children element of your dropdown which will take isActive as a variable :
<Dropdown items="...">
{isActive => <Button active={isActive} />}
</Dropdown>
Then, is the render function, simply call the function and send your state value as a parameter :
render(){
return (
<div className={styles.toggle} onClick={(event) => this.showMenu(event)}>
{this.props.children(this.state.isActive)}
</div>
);
}
<Dropdown >
<Button isActive={this.state.isActive} />
</Dropdown>
In your button get it with this.props.isActive

How to show a block of collapsible text on click of button

I am trying to implement a collapsible component. I have designed it such as, on click of a button, a block of dynamic text will appear. I made a functional component and using the tags in a class. The name of the component is, CustomAccordion.jsx and using this component in Container.jsx
I have tried to create a button and a function for onClick event.
Part of the CustonAccordion.jsx
const handleToggle = () : string =>{
let content = this.nextElementSibling;
if (content.style.maxHeight){
content.style.maxHeight = null;
}else{
content.style.maxHeight = content.scrollHeight +'px';
}
}
export default function CustomAccordion(props: PropType): React.Component<*> {
const { title, children } = props
return(
<div>
<AccordionButton onClick={() => this.handleToggle()}>{title}</AccordionButton>
<AccordionContent>
<p>{children}
</p>
</AccordionContent>
</div>
)
}
Part of calling Container.jsx
<CustomAccordion title = {this.props.name}>
<p>This is the text passed to component.</p>
</CustomAccordion>
<br />
This does not show the expanded text and it seems that the click event does not work properly. I am very new in react, guessing the syntax might be incorrect.
In react you should generally try to avoid touching DOM directly unless you really have to.
Also you are accessing the handleToggle function wrongly. It should be onClick={() => handleToggle()} because this in your case is window/null and so it has no handleToggle method.
Instead you can use a stateful class component to achieve the same thing.
export default class CustomAccordion extends React.Component {
state = {show: false};
toggle = () => this.setState({show: !this.state.show});
render() {
const {title, children} = this.props;
const {show} = this.state;
return (
<div>
<AccordionButton onClick={this.toggle}>{title}</AccordionButton>
{show && (
<AccordionContent>
<p>{children}</p>
</AccordionContent>
)}
</div>
)
}
}
If you want to have some kind of animation, you can set different className based on the show state instead of adding/removing the elements.

Can't get button component value onClick

I'm sure this is something trivial but I can't seem to figure out how to access the value of my button when the user clicks the button. When the page loads my list of buttons renders correctly with the unique values. When I click one of the buttons the function fires, however, the value returns undefined. Can someone show me what I'm doing wrong here?
Path: TestPage.jsx
import MyList from '../../components/MyList';
export default class TestPage extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleButtonClick(event) {
event.preventDefault();
console.log("button click", event.target.value);
}
render() {
return (
<div>
{this.props.lists.map((list) => (
<div key={list._id}>
<MyList
listCollection={list}
handleButtonClick={this.handleButtonClick}
/>
</div>
))}
</div>
);
}
}
Path: MyListComponent
const MyList = (props) => (
<div>
<Button onClick={props.handleButtonClick} value={props.listCollection._id}>{props.listCollection.title}</Button>
</div>
);
event.target.value is for getting values of HTML elements (like the content of an input box), not getting a React component's props. If would be easier if you just passed that value straight in:
handleButtonClick(value) {
console.log(value);
}
<Button onClick={() => props.handleButtonClick(props.listCollection._id)}>
{props.listCollection.title}
</Button>
It seems that you are not using the default button but instead some sort of customized component from another libray named Button.. if its a customezied component it wont work the same as the internatls might contain a button to render but when you are referencing the event you are doing it throug the Button component

Use child components as real components, not stateless child objects

Some component contains a Tabs with Tabs:
class App {
render() {
<div>
<p>Tabs here:</p>
<Tabs>
<Tab name="Page 1"> .. content here .. </Tab>
<Tab name="Page 2"> .. content here .. </Tab>
<Tab name="Page 3"> .. content here .. </Tab>
</Tabs>
</div>
}
}
Tabs is responsible for most of the markup:
class Tabs {
click() {
// I want the Tab component, because I want to tab.setState()
// `this` is not a `Tab` object
}
render() {
return (
( print the tab labels: )
<ul>
{ this.props.children.map(tab =>
// >>> `tab` is not a `Tab` object <<<
<li><a onClick={ this.click.bind(tab) } href="#">{ tab.props.name }</a></li>
) }
</ul>
( print the tab content: )
{ this.props.children }
);
}
}
A Tab itself is very little, maybe nothing:
const Tab = ({children}) => <div>{ children }</div>;
How does Tabs.click know which Tab was clicked. If I click.bind(tab) it's the child object, not the Tab object. If I click.bind(this) it's the Tabs object.
This might be a very longwinded way of asking why props.children don't contain the Component objects, but a kind of proxy (?) child object. It does have the props, but not the methods, state etc.
edit 1:
In this example, I'd like to let Tab decide how to render itself: once as tab link and once as tab content. Tabs would be responsible for calling the render methods, but Tab would know how. As you can see in Tabs.render() there are 2 renders. It'd be nice if it could do this:
<ul>
{ this.props.children.map(tab => tab.renderLink()) }
</ul>
{ this.props.children.map(tab => tab.renderContent()) }
or just
{ this.props.children }
because content is the normal render
But Tabs can't do that, because it doesn't have Tab objects for children... Why?
Well, there are a few methods to achieve this. To start, you're right - since you're using ES6 arrow functions in the map, this isn't rebound - it's "inherited" from the enclosing scope (I think, technically, it's less inherited and more just left alone - unchanged).
First, instead of binding in the render method, bind click in the constructor.
constructor(props) {
super(props);
this.click = this.click.bind(this);
}
or use ES6 Arrow Functions
click = (event) => {...}
Although this doesn't directly solve your issue, it cleans up the context mess that arises from using bound callbacks.
From there, you can get the clicked tab by using event.target.
You can also use partials - the bind method accepts additional parameters which are prepended to the parameters of the bound function. That would look something like this:
render (
<div>
{ this.props.children.map((tab) => {
<a <li><a onClick={ this.click.bind(null, tab) } href="#">{ tab.props.name }</a></li>
}) }
);
you would have to adjust your click method to accept tab as well: click(tab, event)
I also don't think it's wise, or even recommended to mess with a component's state from another component. Why aren't you using the Tab component? Pass in Tabs onClick as a prop and handle click events like this...
class Tab extends React.Component {
onClick = (event) => {
this.setState({...}); // Tab onClick
if (this.props.onClick) this.props.onClick(); // Tabs onClick
}
}
edit: Working example
Tabs class
class Tabs extends React.Component {
onTabClick = (tab) => {
tab.setState({ test: true });
}
render() {
return (
<div>
{ this.props.children.map((tab) => {
return React.cloneElement(tab, {
onClick: this.onTabClick
});
}) }
</div>
);
}
}
Tab class
class Tab extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
onClick = (event) => {
console.log(event);
event.preventDefault();
if (this.props.onClick) {
this.props.onClick(this);
}
}
render() {
return <div>{ !this.state.test && <a href={ this.props.url } onClick={ this.onClick }>{ this.props.label }</a> }</div>;
}
}
example tabs
<Tabs>
<Tab url="http://www.google.com" label="Google" />
<Tab url="http://www.google.com" label="Google" />
</Tabs>
although it begs the question again - why not just set the state inside of the Tab class? Why do you need to set the state of a child from within a parent? Can that state not be maintained by the parent (using this.setState(...) and passed into the child as a prop?

Resources