reactjs resetting props for componentWillReceiveProps - reactjs

First question from my first react project.
I'm trying to figure out how to best effect a component on a specific onClick link, to be able to retrigger this effect and to not have it effected by other links on the page. The first two asks are working, but I can't seem to have other links not effect the component.
I have a DisplayLightbox component on my page accepting a couple values
<div>
<DisplayLightbox
showLightbox = {this.state.showLightbox}
lightboxValue = {this.state.travelCity}
/>
</div>
The link I want to trigger the lightbox is calling a function that sets the state (and sends the prop). This part seems to work fine.
onClick={() => this.showLightbox(el.field_city)}
showLightbox(travelCity){
this.setState({
showLightbox: true,
travelCity: travelCity,
});
}
In my DisplayLightbox component, the componentWillReceiveProps does set state to true, which adds the lb-active class in the div, which, from the css, displays the lightbox div. This seems fine.
class DisplayLightbox extends React.Component {
constructor(props) {
super(props);
this.state = {
showLightbox: false,
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.showLightbox !== this.state.showLightbox) {
this.setState({ showLightbox: nextProps.showLightbox });
}
}
closeLightbox() {
this.setState({
showLightbox: false,
});
}
render() {
var lbActive = (this.state.showLightbox === true) ? "lb-active" : ""
return <div className={"lightbox " + lbActive }>
<div className={"lightbox-close " + lbActive } onClick={() =>
this.closeLightbox()}>CLOSE</div>
Copy in lightbox
</div>;
}
}
Looking into it, I see that since props are not controlled by the component and read-only, once it's set as True and I close the div by setting the state of showLighbox back to false, the nextProps.showLightbox remains true. So, if I close it (closeLightbox) and click a different onClick on my page, it still looks into my component, sees nextProps.showLightbox is still set to TRUE and opens the lightbox.
I only want the lightbox open if that specific link is the one being clicked though. It would seem overkill to have every other link setting the state of showLightbox to false, so I'm guessing I'm not looking at this properly.
Thanks

You could just move your closeLightbox method to upper component and manage showLightbox prop from parent. Then component DisplayLightbox will have 3 props: showLightbox, travelCity and method closeLightbox.
When you move closing lightbox to parent component, event componentWillReceiveProps should be no longer needed.

It would seem overkill to have every other link setting the state of
showLightbox to false, so I'm guessing I'm not looking at this
properly.
Why not configure only the one link you want to turn on / off the lightbox then?
As i see it, a component who gets its active state from the parent or external component, should not bother to manage it in its own state.
You can manage the on / off state in the parent's state and pass down a isOn and onClose event handler to the LightBox.
Once the LightBox was clicked it will invoke the handler passed down to it and the parent will change the state of isOn to false, this will trigger a render with a new prop of isOn for the LightBox this time it's value is false.
While clicking the external link / button the parent will listen to it and change the state of isOn to true, and again isOn will passed down to LightBox with it's shiny new value of true.
Small example:
const cities = ["ny", "tlv", "ark"];
class Button extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { item, onClick } = this.props;
onClick(item);
}
render() {
return <button onClick={this.onClick}>{this.props.children}</button>;
}
}
class LightBox extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { city, onClick } = this.props;
onClick(city);
}
render() {
const { isOn } = this.props;
const css = `lightBoxStyle ${isOn && "onStyle"}`;
return (
<div
className={css}
onClick={this.onClick}>
|
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
turnedOn: []
};
this.on = this.on.bind(this);
this.off = this.off.bind(this);
}
on(city) {
const { turnedOn } = this.state;
if (turnedOn.find(c => c === city)) {
return;
}
const nextState = [...turnedOn, city];
this.setState({ turnedOn: nextState });
}
off(city) {
const nextState = this.state.turnedOn.filter(c => c !== city);
this.setState({ turnedOn: nextState });
}
render() {
const { turnedOn } = this.state;
return (
<div>
{cities.map((city, i) => {
const isOn = turnedOn && turnedOn.includes(city);
return (
<div style={{ display: "inline-block", margin: "0 10px" }}>
<LightBox city={city} isOn={isOn} onClick={this.on} />
<hr />
<Button item={city} onClick={this.off}>
Close the light!
</Button>
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.lightBoxStyle{
border: 1px solid #eee;
width: 30px;
height: 30px;
text-align: center;
box-shadow: 0 0 4px 1px #222;
border-radius: 50%;
margin: 0 auto;
cursor: pointer;
}
.onStyle{
background-color: #333;
color: #fff;
}
<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>
<div id="root"></div>

Based on the great replies, I took the close out of the child and back to the parent.
I did this by adding a lightbox-button div to the parent
<div className={"lightbox-button " + this.state.showLightbox} onClick={() => this.showLightbox()}></div>
<DisplayLightbox
showLightbox = {this.state.showLightbox}
lightboxValue = {this.state.travelCity} />
This calls the same function showLightbox, that I modified slightly to toggle
showLightbox(travelCity){
this.setState({
showLightbox: !this.state.showLightbox,
travelCity: travelCity,
});
}
Using css, I keep this lightbox-button hidden, unless this.state.showLightbox is true. When that happens, the button displays and, when click, toggles the showLightbox state, thereby removing the lightbox and button.
Not sure if this is the ideal solution, but it seems to be working.

Related

Reactjs state changes do not propagate to dynamically-created children components

I'm learning react and while working on a bigger project I created this mockup to show the issue I'm having.
The parent component maintains a value in state which it passes to children via props. I want this value to propagate to children and update there when it is changed in the parent state. This works in the first version of this code:
import React from "react"
import Child from './Child'
export default class Parent extends React.Component {
constructor() {
super();
this.state = {
single_val: false,
}
}
render() {
return(
<div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
<p>Parent val: {this.state.single_val.toString()}</p>
<Child parent_val={this.state.single_val}/>
<Child parent_val={this.state.single_val}/>
<Child parent_val={this.state.single_val}/>
<div className="switch"
style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
onClick={(e)=>{this.setState({single_val: true})}}
>
</div>
</div>
)
}
}
However, in the final version of the project, I need to create the children dynamically. I do it like this:
import React from "react"
import Child from './Child'
export default class Parent extends React.Component {
constructor() {
super();
this.state = {
single_val: false,
children_divs: [],
}
this.setUp = this.setUp.bind(this);
}
componentDidMount() {
this.setUp();
}
setUp() {
var baseArray = [...Array(3)];
var children = baseArray.map((elem)=>{
return (<Child parent_val={this.state.single_val} />)
});
this.setState({children_divs: children});
}
render() {
return(
<div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
<p>Parent val: {this.state.single_val.toString()}</p>
{this.state.children_divs}
<div className="switch"
style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
onClick={(e)=>{this.setState({single_val: true})}}
>
</div>
</div>
)
}
}
...and the value no longer propagates to children when I press the button and change the parent's state: results screenshots.
How to keep the dynamic creation of child divs and still have the parent value propagate? I sense the issue might me because the value and children divs array are both maintained in the parent state but I'm not sure how to fix it. Hours of searching and looking at examples suggest I should recreate children divs from scratch - run the setUp again - but it seems like an overkill for one state value that I thought should propagate anyway.
Child component code for reference:
import React from "react"
export default function Child(props) {
return (
<div className="Child">
<p>Child val: {props.parent_val.toString()}</p>
</div>
)
}
P.S. I even experimented with adding componentDidUpdate() to children to try and receive props again, but it never triggered.
Ok, so the problem here is your children_divs are created once and value of single_val is added/sent to them at that time (when you have created them in setUp function.
Solution is simple, have your children created in render function, as render is called each time your state changes. This also removes your children_divs from state as its only used to render and serve no other purpose.
import React from "react"
import Child from './Child'
export default class Parent extends React.Component {
constructor() {
super();
this.state = {
single_val: false,
}
}
render() {
var baseArray = [...Array(3)];
var children_divs= baseArray.map((elem)=>{
return (<Child parent_val={this.state.single_val} />)
});
return(
<div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
<p>Parent val: {this.state.single_val.toString()}</p>
{children_divs}
<div className="switch"
style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
onClick={(e)=>{this.setState({single_val: true})}}
>
</div>
</div>
)
}
}

In React JS, how do I tell a parent component that something has happened in the child?

I have a React JS app with a simple hierarchy: ContainingBox wraps two InfoBox components. in this example, I simply want to tell the ContainingBox component 1) that something has been clicked, and 2) which InfoBox (by label name) has been clicked?
Here is some basic code that works in my browser to get this question up & running. All it does it console.log when you click onto one of the InfoBox elements on the page.
Essentially, what I am trying to achieve is that I want the ContainingBox to change state (specifically, border color as rendered) when one of the child InfoBox elements is clicked.
I'm not sure what the right direction here is.
I built this app with React 16.10.2, but I would be happy to read answers pointing me towards the latest 'React way' of thinking.
import React from 'react';
import styled from 'styled-components'
import './App.css';
const StyledInfoBox = styled.div`
width: 100px;
border: solid 1px green;
padding: 10px;
cursor: pointer;
`
class InfoBox extends React.Component {
constructor({blurb}) {
super()
this.state = {
label: (blurb ? blurb.label : ""),
}
this.selectBox = this.selectBox.bind(this);
}
selectBox(e) {
e.preventDefault()
console.log("selectBox")
// how do I tell the ContainingBox component 1) that something has been clicked,
// and 2) which InfoBox (by label name) has been clicked?
}
render() {
const {label} = this.state
return (
<StyledInfoBox onClick={this.selectBox} >
{label}
</StyledInfoBox>
)
}
}
class ContainingBox extends React.Component {
render() {
return (
<div>
<InfoBox key={1} blurb={{label: "Aenean malesuada lorem"}} />
<InfoBox key={2} blurb={{label: "Lorem Ipsum dor ameet"}} />
</div>
)
}
}
function App() {
return (
<div className="App">
<ContainingBox />
</div>
)
}
export default App;
You pass a callback from the parent component to child component via the props.
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
changeNameTo = (newName) => this.setState({name: newName})
render() {
return (
<div>
<h1>{this.state.name}</h1>
<p>
<Child callbackExample={this.changeNameTo} />
</p>
</div>
);
}
}
Then you have your Child component.
class Child extends Component {
render() {
return(
<div>
<button onClick={() => this.props.callbackExample("Doggos")}>
Click me
</button>
</div>)
}
}
When you click the button, the callback is invoked setting the state of the parent, which is then reflected when the parent re-renders.

React Child and Parent Components state change on click

I am trying to make a click on a button of a child React component change the Boolean state of the child and of its parent.
The issue here is that it has to change states of both components.
Here is a link for the code I am trying to get working:
https://stackblitz.com/edit/child-to-parent-state-pass-bkmvwc?file=Child.js
The requirement is to click the hamburger button and it changes the state of the child component (the actual hamburger button) and its parent component.
Thank you!
I would not recommend doing what you are doing.
But, knowing nothing of your background I will only answer your question.
In parent.js you are missing the bind to this.
Use this line instead and check if this works
Why don't you manage the state from the parent component like:
Parent.js:
import React from 'react'
import { Link } from 'react-router-dom'
import { initializeIcons } from '#uifabric/icons';
import Hamburger from './Child'
initializeIcons();
export default class NavBar extends React.Component {
constructor(props) {
super(props);
this.state = {
opened: false
};
}
handleCounter = () => {
this.setState({ opened: !this.state.opened });
}
render() {
return (
<Hamburger
opened={this.state.opened}
handleCounter={this.handleCounter}
/>
);
}
}
Child.js
import React from 'react'
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { initializeIcons } from '#uifabric/icons';
initializeIcons();
export default class Hamburger extends React.Component {
constructor(props) {
super(props);
}
updateParent() {
this.props.handleCounter(this.state);
}
render() {
return (
<IconButton
checked={this.props.opened}
iconProps={{ iconName: (this.props.opened ? 'Cancel' : 'GlobalNavButton'), style: { fontSize: 35 } }}
className="hamburger mobile-only"
title="Open Global Navigation"
ariaLabel="Open Global Navigation"
styles={{
root: {
padding: '0',
border: 'none',
background: 'transparent !important'
}
}}
onClick={this.props.handleCounter}
/>
);
}
}
PS: I removed the comments for readability
You would only change the state of the parent. The child would just read the props that are passed to it.
Parent component
constructor(props){
super(props)
this.state = {
hamburgerOpen: false
}
}
handleHamburgerToggle = () => {
let { hamburgerOpen } = this.state;
this.setState({
hamburgerOpen: !hamburgerOpen
})
}
render() {
let { hamburgerOpen } = this.state;
return (
<Child
hamburgerOpen={hamburgerOpen}
handleHamburgerToggle={this.handleHamburgerToggle}
/>
)
}
Child will have access to the props passed to it. You can make the Hamburger a functional component as well since it isn't concerned about the current state, only the parent is.
hamburgerOpen and toggleHamburgerOpen
Child Component
const { handleHamburgerToggle } = this.props;
return {
<div>
<div
onClick={() => handleHamburgerToggle()}
>
Click me to toggle hamburger
</div>
</div>
}

Change element content with onClick in React

In my application I have multiple blocks generated dynamically and each one of them has an onClick event.
My goal is to be able to change the contents of the div when the click happens.
Is there a way to do this thru event.target property of the onClick event?
Or should i create a ref for each div upon creation and then work with refs?
Or should i create an array of Div elements in component state and search&modify the element later re-rendering all divs from array?
Since blocks are generating dynamically, have onClick event on children components.
const Parent = () => {
return (
<Child content={content} contentAfterClick={content} />
<Child content={content} contentAfterClick={content} />
)
}
class Child extends Component {
constructor() {
super();
this.state ={
read: false,
};
}
render() {
if (this.state.read) {
return(
<div>{this.props.contentAfterClick}</div>
)
}
return (
<div onClick={() => this.setState({ read: true })}>
<div>{this.props.content}</div>
</div>
);
};
}
This demo illustrates how you can change the contents of a div, the text, when a click happens through the onClick and event.target object as you wanted.
You can do this through the use of refs, but normally you want to avoid refs unless absolutely necessary because there are easier ways to accomplish the same thing in React.
Also wouldn't want to keep the physical DOM nodes, HTMLDivElement, in state. Instead, keep the contents it relies upon in state (in our case a single number value), then when you change the contents it will automatically update and rerender our div nodes.
// Example class component
class Container extends React.Component {
constructor(props) {
super(props);
const blocks = [];
blocks.push(0);
blocks.push(0);
blocks.push(0);
this.state = { blocks: blocks, clickedElementContents: "" };
}
increment(event, index) {
const newBlocks = this.state.blocks;
newBlocks[index]++;
this.setState({ blocks: newBlocks, clickedElementContents: event.target.innerText });
}
render() {
return (
<div>
<div className="block" onClick={(event) => { this.increment(event, 0) }}>Click me! ({this.state.blocks[0]})</div>
<div className="block" onClick={(event) => { this.increment(event, 1) }}>Click me! ({this.state.blocks[1]})</div>
<div className="block" onClick={(event) => { this.increment(event, 2) }}>Click me! ({this.state.blocks[2]})</div>
<span>Contents of the clicked element: {this.state.clickedElementContents}</span>
</div>
);
}
}
// Render it
ReactDOM.render(
<Container/>,
document.body
);
.block {
display: inline-block;
background-color: black;
color: white;
padding: 5px;
margin-right: 10px;
}
<div id="react"></div>
<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>

Re-using React Components

I've been doing React lessons, but one thing I don't ever see done is reusing components. For example, if I had a button, and wanted to produce a div every time that button was clicked. How would I do it using a React component that's sole purpose is rendering a single div, And that button uses that one component to add additional divs to the page every time it's clicked?
Do you mean, something like this?
The CustomButton stateless component (presentational) just receives props and can be disabled, text can be changed and a callback can be defined. It doesn't have any own state and can be reused throughout your app where you might need a button.
The ButtonSampleApp is a container component that uses the presentional component and supplies it with a callback, and then handles that callback. To add a div in it's rendering. The ButtonSampleApp uses component state to achieve this
const CustomButton = ({ text, callback, isEnabled }) => {
return <button onClick={() => callback()} disabled={!isEnabled} type="button">{ text }</button>;
};
class ButtonSampleApp extends React.Component {
constructor() {
super();
this.state = {
divs: []
};
}
onButtonClicked() {
const { divs } = this.state;
this.setState( { divs: [...divs, { text: divs.length }] });
}
render() {
const { max } = this.props;
const { divs } = this.state;
return (<div>
<h1>Click on button to add a max of { max } divs</h1>
<div>
{ divs && divs.map( ({text}) => <div key={text}>{ text }</div> ) }
</div>
<CustomButton isEnabled={!divs || divs.length < max} text="Add button" callback={() => this.onButtonClicked()} />
</div>);
}
}
ReactDOM.render( <ButtonSampleApp max={10} />, document.querySelector('#container') );
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<div id="container"></div>

Resources