Using state in child component received from parent component - javascript

When I click on the menu in GeneralNav I successfully switch between true or false.
This menuState once again is passed successfully to Overlay via HomePage.
Though I'm not able to toggle the right classes in Overlay to hide or show the menu. Can someone explain me a correct workflow to add the classes on my EasyFlexCol component to show or hide it? Been stuck for a while now.
Thanks!
class GeneralNav extends Component {
render() {
return (
<div
className="nav-burger-box menu-action"
onClick={this.props.toggleMenu}
>
<div className="nav-burger-top" />
<div className="nav-burger-bottom" />
</div>
);
}
}
class HomePage extends Component {
state = {
showMenu: false
};
toggleMenu = e => {
e.preventDefault();
this.setState(state => ({ showMenu: !state.showMenu }));
};
render() {
return (
<React.Fragment>
<OverlayMenu menuState={this.state.showMenu}/>
<HeaderFullscreen />
</React.Fragment>
);
}
}
class OverlayMenu extends Component {
state = {
showMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper display-block",
hideMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper"
};
render() {
let menuState = this.props.menuState
console.log(menuState)
return (
<EasyFlexCol style={"here I want to add the right class to show or hide the overlay menu"}>
</EasyFlexCol>
);
}
}

You can do using ternary operation like this :
i.e if menuState is true show showMenu else vice versa
<EasyFlexCol className={menuState ? showHide.showMenu : showHide.hideMenu}>
</EasyFlexCol>
here is working example for your refrence : https://stackblitz.com/edit/react-wj49ol

Use ternary operator when rendering.
class OverlayMenu extends Component {
render() {
const showHide= { // Assuming that strings below are valid CSS class names
showMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper display-block",
hideMenu: "overlay-menu-wrapper bg-color-dark overlay-menu-wrapper"
};
let menuState = this.props.menuState
console.log(menuState)
return (
<EasyFlexCol className={menuState ? showHide.showMenu : showHide.hideMenu}>
</EasyFlexCol>
);
}
}
Alternatively you can compose style of <EasyFlexCol/> component dynamically
class OverlayMenu extends Component {
render() {
style={ display: 'block' }
let menuState = this.props.menuState
return (
<EasyFlexCol className={'some-default-class'} style={menuState ? style : {}}>
</EasyFlexCol>
);
}
}
Both example assume that <EasyFlexCol/> has either className property or style property.

Related

How to move an index of a clicked item to another component that is not a parent?

Expecting effect: click <li> --> take index --> send this index to component Watch.
When I click <li>, I grab the index and move it to theWatch component. However, when I click the second li it returns the index of the one I clicked for the first time. I think this is because it updates this index via componentDidMount. How can I reference this index after componentDidMount?
Todo
class Todo extends Component {
render () {
return (
<div className = "itemTodos" onClick={()=> this.props.selectTodo(this.props.index)}>
</div>
)
}
}
export default Todo;
App
class App extends Component {
constructor(){
super();
this.state {
selectedTodoIndex: index
}
}
selectTodo = (index) => {
this.setState({
selectedTodoIndex: index
})
}
render () {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
selectTodo ={this.selectTodo}
/>
)
}
</ul>
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
</div>
)
}
}
export default App;
Watch
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
First of all you you use selectedTodoIndex in
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
but it not specified in your render code. Add
const {selectedTodoIndex} = this.state;
in render function.
Second, use componentDidUpdate in Watch for update inner state on props update:
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
componentDidUpdate (prevProps) {
if (prevProps.selectedTodo !== this.props.selectedTodo)
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
If i am not wrong your Todo component is in watch??. So Watch component should be like this :
render () {
return (
<div>
<Todo index={this.state.selectedIndex} selectedTodo={this.props.selectedTodoIndex}/>
</div>
)
}
Here i made codesandbox of this code . Feel free to checkout and let me know if you any doubt. Code link : https://codesandbox.io/s/frosty-chaplygin-ws1zz
There are lot of improvements to be made. But I believe what you are looking for is getDerivedStateFromProps lifeCycle method in Watch Component. So the code will be:
getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.selectedTodoIndex !== prevState.selectedTodoIndex) {
return { selectIndex: nextProps.selectedTodoIndex }
}
}
This will check if the selected index has changed in App Component, if yes it will update the state in Watch Component.

How to make an hover state HOC without wraping the component inside a <div>?

I'm trying to write a Higher-order Component which provide a hover property to a component. But I can't make it work with any type of component.
My HOC
function withHover(WrappedComponent) {
return class extends Component {
state = {
hover: false
}
onMouseEnter(){
this.setState({hover: true});
}
onMouseLeave(){
this.setState({hover: false});
}
render() {
return (
<div onMouseEnter={this.onMouseEnter.bind(this)} onMouseLeave={this.onMouseLeave.bind(this)}>
<WrappedComponent {...this.props} hover={this.state.hover}/>
</div>
)
}
}
}
My problem is that I have to wrap the component inside a div for the OnMouse events to work. But when I want, for example, make a <tr> inside a <table> hoverable the <tr> will be wrapped into a <div> which break the <table> logic.
I have thought of passing the HOC's OnMouse events handlers to the wrapped component and call them inside it, but that is not quite convenient because the aim of all this is to save developpement time
So my question is : How to rewrite this HOC to avoid wrapping the initial component inside a <div> ?
Thanks for your time
You can just render WrappedComponent from your HOC and passs onMouseEnter and onMouseLeave functions as props and then use them in wrapped components by spread operator on props
Code be like:
function withHover(WrappedComponent) {
return class extends Component {
state = {
hover: false
}
onMouseEnter = () => {
this.setState({hover: true});
}
onMouseLeave = () => {
this.setState({hover: false});
}
render() {
return <WrappedComponent onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} {...this.props} hover={this.state.hover}/>
}
}
}
const TableRow = props => {
return (
<tr {...props}>
{props.children}
</tr>
)
}
const TableRowWithHover = withHover(TableRow);
Wrap the child component in fragment in parent Component
pass the events to the child elm
Child Elm
class Child extends Component {
render() {
return (
<div
onMouseLeave={this.props.onMouseLeave || null}
onMouseEnter={this.props.onMouseEnter || null}
>
This is a child Component
</div>
);
}
}
Parent Component(wrapper)
import Child from './child';
class Parent extends Component {
state = { }
onMouseEnter = () =>{
console.log("mosuse Entered child")
}
onMouseLeave = () =>{
console.log("mosuse left child")
}
render() {
return (
<>
<Child onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}></Child>
</>
);
}
}

ReactJS selecting an element uniquely from a map

I am doing a todo app to practice React. I hit a blocker and now I'm trying to figure out how to uniquely edit a card.
Currently when I click on edit, all my cards are set to isEditing == true. I've tried adding a key and index, but doesn't seem to uniquely identify the selected card.
As seen in my gif:
Obviously the expected outcome is that it should only set isEditing == true to the selected card.
See Code below.
For more context: there is stateful component that passes the props to this component, I'm using react-bootstrap (hence Panel, Button), and I removed some code for brevity (construct and whatnot).
edit() {
this.setState({
isEditing: true
})
}
renderEditDoneButtons() {
return (
<div>
<Button onClick={this.edit}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.isEditing ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
All three are changing based on your single isEditing state, which is why you're seeing all three being shown when you click any of the "Edit" buttons. Instead of a single isEditing key in state, use an array to maintain all three states like so:
constructor(props) {
super(props);
// Sets a true/false editing state for all three panels
this.state = {
editingPanels: Array(3).fill(false)
}
}
edit(i) {
// Switches editing state to false/true for given i
const editingPanels = this.state.editingPanels.slice();
editingPanels[i] = !editingPanels[i];
this.setState({
editingPanels: editingPanels
})
}
renderEditDoneButtons(i) {
return (
<div>
<Button onClick={()=>this.state.edit(i)}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.editingPanels[i] ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
You can use a separate component for each todo list item and use it inside the map method.The following example gives an idea on how to implement this.I am using another example as you have not provided the full code.
class EditText extends React.Component {
constructor(props) {
super(props)
this.state = {value:props.data,newValue:'hi'}
this.editValue = this.editValue.bind(this)
}
editValue() {
this.setState({value:this.state.newValue})
}
render() {
return(
<div>
{this.state.value}
<button onClick={this.editValue}>Change text to Hi</button>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {tempDate : ['hello','how']}
}
render() {
return (
<div className="App">
{this.state.tempDate.map(data=>(<EditText data={data}/>))}
</div>
);
}
}
You need to have state variable isEditing for each particular card.
If there are 3 cards, you need to have 3 variables.
Edit 1 :-
Example is already shared by Kody R.
One Thing i noticed is instead of hard-coding array size to 3,we could assign array size by number of notes recieved in props.
this.state = {
editingPanels: Array(3).fill(false)
}
To
this.state = {
editingPanels: Array(this.props.notes.length).fill(false)
}
Hope this helps,
Cheers !!

rendering multiple elements after onClick event in React

I'm having problems trying to render two react elements inside a react component after a onClick event. Wondering if that's even possible? I'm sure I'm messing up the ternary operator, but I cannot think on another way to do what I'm trying to do ?
TL;DR: "When I click a button I see elementA and elementB"
Here is a snippet of the code:
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: true };
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
console.log(`current state: ${this.state.showElement} and prevState: ${this.prevState}`);
this.setState(prevState => ({ showElement: !this.state.showElement }) );
};
elementA() {
<div>
<h1>
some data
</h1>
</div>
}
elementB() {
<div>
<h1>
some data
</h1>
</div>
}
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === true}>
</button>
{ this.state.showElement
?
null
:
this.elementA() && this.elementB()
}
</section>
)
}
}
export default MyComponent;
You just inattentive.
elementA() {
return ( // You forget
<div>
<h1>
some data
</h1>
</div>
)
}
And the same in element B.
And if You want to see both components you should change Your ternary to
{ this.state.showElement
?
<div> {this.elementA()} {this.elementB()}</div>
:
null
}
Another "and", for toggling showElement in state just enough
this.setState({showElement: !this.state.showElement });
Try this instead, (I will add comments into the code trying to explain what's going on):
function SomeComponentName() { // use props if you want to pass some data to this component. Meaning that if you can keep it stateless do so.
return (
<div>
<h1>
some data
</h1>
</div>
);
}
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: false }; // you say that initially you don't want to show it, right? So let's set it to false :)
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
this.setState(prevState => ({ showElement: !prevState.showElement }) );
// As I pointed out in the comment: when using the "reducer" version of `setState` you should use the parameter that's provided to you with the previous state, try never using the word `this` inside a "reducer" `setState` function
};
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === false}>
</button>
{ this.state.showElement
? [<SomeComponentName key="firstOne" />, <SomeComponentName key="secondOne" />]
: null
}
</section>
)
}
}
export default MyComponent;

React not updating style

I'm trying to change style when a button is clicked using React. I can see value is changing when button is clicked, but style is not changing. I've been writing in many ways with no luck.
export const Paragraph = () => {
var state = 'none'
const changeState = () => {
state = state == 'none' ? 'inline-block' : 'none'
}
return (
<div>
<p style={{display: state}}</p>
</div>
)
}
Better way to set a class instead inline styles.
class Paragraph extends React.Component{
constructor(){
super();
this.state = {
isClicked: false
};
}
onClick(){
let condition = this.state.isClicked;
this.setState({isClicked: !condition})
}
render(){
return (
<div onClick={this.onClick.bind(this)}>
<p className={this.state.isClicked? "class_1" : "class_2"}></p>
</div>
);
}
}

Categories