Are props passed from parent to child by default with `this`? - javascript

I am learning through an open source project here. I have deployed it and it works. So the below pasted code is valid for sure.
I was looking at a Header component in Header.js:
class Header extends React.Component {
state = {
open: false,
};
render() {
const {
classes,
toggleDrawerOpen,
margin,
turnDarker,
} = this.props;
return (
.... some code ....
)
I see that classes is passed as a prop from the parent. So I looked into the parent component, Dashboard. Here is the code:
import { Header, Sidebar, BreadCrumb } from './../../components';
import { toggleAction, openAction, playTransitionAction } from './../../actions/UiActions';
import styles from './appStyles-jss';
class Dashboard extends React.Component {
state = {
transform: 0,
};
componentDidMount = () => {
// Scroll content to top
const mainContent = document.getElementById('mainContent');
mainContent.addEventListener('scroll', this.handleScroll);
// Set expanded sidebar menu
const currentPath = this.props.history.location.pathname;
this.props.initialOpen(currentPath);
// Play page transition
this.props.loadTransition(true);
// Execute all arguments when page changes
this.unlisten = this.props.history.listen(() => {
mainContent.scrollTo(0, 0);
setTimeout(() => {
this.props.loadTransition(true);
}, 500);
});
}
componentWillUnmount() {
const mainContent = document.getElementById('mainContent');
mainContent.removeEventListener('scroll', this.handleScroll);
}
handleScroll = (event) => {
const scoll = event.target.scrollTop;
this.setState({
transform: scoll
});
}
render() {
const {
classes, // classes is here
route,
toggleDrawer,
sidebarOpen,
loadTransition,
pageLoaded
} = this.props;
const darker = true;
return (
<div className={classes.appFrameInner}>
// NOTE: Header component is here but I don't see how classes is passed to it.
<Header toggleDrawerOpen={toggleDrawer} turnDarker={this.state.transform > 30 && darker} margin={sidebarOpen} />
<Sidebar
open={sidebarOpen}
toggleDrawerOpen={toggleDrawer}
loadTransition={loadTransition}
turnDarker={this.state.transform > 30 && darker}
/>
<main className={classNames(classes.content, !sidebarOpen && classes.contentPadding)} id="mainContent">
<div className={classes.bgbar} />
</main>
</div>
);
}
}
You can see that the classes prop is passed from Dashboard's parent. However, I was expecting some syntax that shows it is passed into the child Header component.
See the "NOTE" line in the code, nothing was said about passing the entire props to Header component or passing the const classes specifically to Header.
How is classes passed from parent (Dashbaord) to child (Header)?

The classes prop is not passed from parent Dashboard to child Header.
The classes prop is made available directly to your Header component using the wrapping withStyles HOC when exporting your component:
export default withStyles(styles)(Header);
This approach is commonly known as CSS-in-JS and you can read more details in the material-ui styles documentation.

Related

How to use forEach in react js

I want to create a function which iterate over all element with same class and remove a specific class.
It could be done easily using JavaScript.
const boxes = document.querySelectorAll(".box1");
function remove_all_active_list() {
boxes.forEach((element) => element.classList.remove('active'));
}
But how can I do this similar thing is ReactJs. The problem which I am facing is that I can't use document.querySelectorAll(".box1") in React but, I can use React.createRef() but it is not giving me all elements, it's only giving me the last element.
This is my React Code
App.js
import React, { Component } from 'react';
import List from './List';
export class App extends Component {
componentDidMount() {
window.addEventListener('keydown', this.keypressed);
}
keypressed = (e) => {
if (e.keyCode == '38' || e.keyCode == '40') this.remove_all_active_list();
};
remove_all_active_list = () => {
// boxes.forEach((element) => element.classList.remove('active'));
};
divElement = (el) => {
console.log(el);
el.forEach((element) => element.classList.add('active'))
};
render() {
return (
<div className="container0">
<List divElement={this.divElement} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import data from './content/data';
export class List extends Component {
divRef = React.createRef();
componentDidMount() {
this.props.divElement(this.divRef)
}
render() {
let listItem = data.map(({ title, src }, i) => {
return (
<div className="box1" id={i} ref={this.divRef} key={src}>
<img src={src} title={title} align="center" alt={title} />
<span>{title}</span>
</div>
);
});
return <div className="container1">{listItem}</div>;
}
}
export default List;
Please tell me how can I over come this problem.
The short answer
You wouldn't.
Instead you would conditionally add and remove the class to the element, the component, or to the collection.map() inside your React component.
Example
Here's an example that illustrates both:
import styles from './Example.module.css';
const Example = () => {
const myCondition = true;
const myCollection = [1, 2, 3];
return (
<div>
<div className={myCondition ? 'someGlobalClassName' : undefined}>Single element</div>
{myCollection.map((member) => (
<div key={member} className={myCondition ? styles.variant1 : styles.variant2}>
{member}
</div>
))}
</div>
);
};
export default Example;
So in your case:
You could pass active prop to the <ListItem /> component and use props.active as the condition.
Alternatively you could send activeIndex to <List /> component and use index === activeIndex as the condition in your map.
Explanation
Instead of adding or removing classes to a HTMLElement react takes care of rendering and updating the whole element and all its properties (including class - which in react you would write as className).
Without going into shadow dom and why react may be preferable, I'll just try to explain the shift in mindset:
Components do not only describe html elements, but may also contain logic and behaviour. Every time any property changes, at the very least the render method is called again, and the element is replaced by the new element (i.e. before without any class but now with a class).
Now it is much easier to change classes around. All you need to do is change a property or modify the result of a condition (if statement).
So instead of selecting some elements in the dom and applying some logic them, you would not select any element at all; the logic is written right inside the react component, close to the part that does the actual rendering.
Further reading
https://reactjs.org/docs/state-and-lifecycle.html
Please don't hessitate to add a comment if something should be rephrased or added.
pass the ref to the parent div in List component.
...
componentDidMount() {
this.props.divElement(this.divRef.current)
}
...
<div ref={this.divRef} className="container1">{listItem}</div>
then in App
divElement = (el) => {
console.log(el);
el.childNodes.forEach((element) => element.classList.add('active'))
}
hope this will work. here is a simple example
https://codesandbox.io/s/staging-microservice-0574t?file=/src/App.js
App.js
import React, { Component } from "react";
import List from "./List";
import "./styles.css";
export class App extends Component {
state = { element: [] };
ref = React.createRef();
componentDidMount() {
const {
current: { divRef = [] }
} = this.ref;
divRef.forEach((ele) => ele?.classList?.add("active"));
console.log(divRef);
window.addEventListener("keydown", this.keypressed);
}
keypressed = (e) => {
if (e.keyCode == "38" || e.keyCode == "40") this.remove_all_active_list();
};
remove_all_active_list = () => {
const {
current: { divRef = [] }
} = this.ref;
divRef.forEach((ele) => ele?.classList?.remove("active"));
// boxes.forEach((element) => element.classList.remove('active'));
console.log(divRef);
};
render() {
return (
<div className="container0">
<List divElement={this.divElement} ref={this.ref} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from "react";
import data from "./data";
export class List extends Component {
// divRef = React.createRef();
divRef = [];
render() {
let listItem = data.map(({ title, src }, i) => {
return (
<div
className="box1"
key={i}
id={i}
ref={(element) => (this.divRef[i] = element)}
>
<img src={src} title={title} align="center" alt={title} width={100} />
<span>{title}</span>
</div>
);
});
return <div className="container1">{listItem}</div>;
}
}
export default List;
Create ref for List component and access their child elements. When key pressed(up/down arrow) the elements which has classname as 'active' will get removed. reference

Render unique divs for each hovered element

minimum reproducible example: https://codesandbox.io/s/react-hover-example-tu1eu?file=/index.js
I currently have a new element being rendered when either of 2 other elements are hovered over. But i would like to render different things based upon which element is hovered.
In the example below and in the codepen, there are 2 hoverable divs that are rendered; when they are hovered over, it changes the state and another div is rendered. I would like for the HoverMe2 div to render text "hello2". Currently, whether i hover hoverme1 or 2, they both just render the text "hello".
import React, { Component } from "react";
import { render } from "react-dom";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false
};
}
handleMouseHover() {
this.setState(this.toggleHoverState);
}
toggleHoverState(state) {
return {
isHovering: !state.isHovering
};
}
render() {
return (
<div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>hello</div>}
</div>
);
}
}
render(<HoverExample />, document.getElementById("root"));
You need to keep the state of item which you have hovered that's for sure
const { Component, useState, useEffect } = React;
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
values: ['hello', 'hello2'],
value: 'hello'
};
}
handleMouseHover({target: {dataset: {id}}}) {
this.setState(state => {
return {
...state,
isHovering: !state.isHovering,
value: state.values[id]
};
});
}
render() {
return (
<div>
<div
data-id="0"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
data-id="1"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.value}</div>}
</div>
);
}
}
ReactDOM.render(
<HoverExample />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
You can pass the context text as shown in example. This is working code:
import React, { Component } from "react";
import { render } from "react-dom";
// Drive this using some configuration. You can set based on your requirement.
export const HOVER_Hello1 = "Hello1";
export const HOVER_Hello2 = "Hello2";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
contextText: ""
};
}
handleMouseHover = (e, currentText) => {
this.setState({
isHovering: !this.state.isHovering,
contextText: currentText
});
}
toggleHoverState(state) {
//
}
render() {
return (
<div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello1)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello1)}
>
Hover Me
</div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello2)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello2)}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.contextText}</div>}
</div>
);
}
}
export default HoverExample;
If the whole point is about linking dynamically messages to JSX-element you're hovering, you may store that binding (e.g. within an object).
Upon rendering, you simply pass some anchor (e.g. id property of corresponding object) within a custom attribute (data-*), so that later on you may retrieve that, look up for the matching object, put linked message into state and render the message.
Following is a quick demo:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const data = [
{id:0, text: 'Hover me', message: 'Thanks for hovering'},
{id:1, text: 'Hover me too', message: 'Great job'}
]
class HoverableDivs extends Component {
state = {
messageToShow: null
}
enterHandler = ({target:{dataset:{id:recordId}}}) => {
const {message} = this.props.data.find(({id}) => id == recordId)
this.setState({messageToShow: message})
}
leaveHandler = () => this.setState({messageToShow: null})
render(){
return (
<div>
{
this.props.data.map(({text,id}) => (
<div
key={id}
data-id={id}
onMouseEnter={this.enterHandler}
onMouseLeave={this.leaveHandler}
>
{text}
</div>
))
}
{
this.state.messageToShow && <div>{this.state.messageToShow}</div>
}
</div>
)
}
}
render (
<HoverableDivs {...{data}} />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
As #CevaComic pointed out, you can do this with CSS. But if you want to use React, for example, because your actual problem is more complex, here is the answer.
You will need a way to tell apart the two elements. It could be done with some neat tricks, like setting an unique id to each element, passing a custom argument, or something else.
But I would advise against "cool tricks" as it's more difficult to understand what is going on, and the code is more prone to errors. I think the best way it to use a dumb approach of unique functions for unique elements.
Each onMouseEnter and onMouseLeave has to be an unique function (e.g. handleMouseHover1 and handleMouseHover2), and each of those functions need to control unique state (for example, isHovering1 and isHovering2). Then you have to render the element you want based on the state. Of course, for a real-world code, you will probably want to use more descriptive names to make the code more comprehensible. The full code would look something like this.
class HoverExample extends Component {
state = {
isHovering1: false,
isHovering2: false
};
handleMouseHover1 = () => {
this.setState(({ isHovering1 }) => ({ isHovering1: !isHovering1 }));
};
handleMouseHover2 = () => {
this.setState(({ isHovering2 }) => ({ isHovering2: !isHovering2 }));
};
render() {
const { isHovering1, isHovering2 } = this.state;
return (
<div>
<div
onMouseEnter={this.handleMouseHover1}
onMouseLeave={this.handleMouseHover1}
>
Hover Me1
</div>
<div
onMouseEnter={this.handleMouseHover2}
onMouseLeave={this.handleMouseHover2}
>
Hover Me2
</div>
{isHovering1 && <div>hello1</div>}
{isHovering2 && <div>hello2</div>}
</div>
);
}
}
Also, updated example: https://codesandbox.io/s/react-hover-example-rc3h0
Note: I have also edited the code to add some syntax sugar which exists with newer ECMAScript versions. Instead of binding the function, you can use the arrow function format, e.g. fn = () => { ... }. The arrow function means the this context is automatically bound to the function, so you don't have to do it manually. Also, you don't have to initialize this.state inside the constructor, you can define it as a class instance property. With those two things together, you do not need the constructor at all, and it makes the code a bit cleaner.

React Architecture: I have a common middle component that needs to render custom child components

I'm making a dashboard that uses a common grid component. The grid has its own functionality separate and is used in other areas of the app. It needs to render a custom component within each grid item and has an active component that also renders a custom component, these custom components use functions from the Grids parent component, whatever is rendering it, below is how I do it current, but I'm pretty sure there is a better way of doing it.
Parent component that renders grid and passes down components and functions
import Grid from './common/grid'
class dashboard extends Component {
gridItemSpecificFunction() { console.log('success') }
activeFunction() { console.log('success again') }
render() {
return <Grid
CustomComponent={ CustomComponent }
ActiveComponent={ ActiveComponent }
activeFunctions={ {activeFunction} }
gridItemFunctions={ { gridItemSpecificFunction:this.gridItemSpecificFunction } }
/>
}
}
Grid that renders custom active and grid items based on data its passed
class Grid extends Component {
render() {
const {CustomComponent} = this.props
return (
<GridWrapper>
{ this.props.dynamicData.map( data => (
<GridItemWrapper>
<CustomComponent { ...data } functions={ this.props.gridItemFunctions } />
</GridItemWrapper>
) )
{ active && < ActiveComponent { ...activeData }
functions={ this.props.activeFunctions }/> }
</GridWrapper>
}
)
}
}
example of custom component that is using function passed through grid item
class CustomComponent extends Component {
render() {
const {gridItemSpecificFunction} = this.props.functions
return (
<div onClick={ gridItemSpecificFunction }>
{ this.props.text }
<div>
}
)
}
}
You actually doing great, of course there is another way to do this, probably is better just because become easier to modify, so you probably should use Context hook to get this done, so great packages based their functionalities in Context API Hook, so a great approach would be this
import React, { useContext, createContext, useMemo } from 'react';
const FunctionalityContext = createContext({}); // just leave it as empty state
// create a hook that return the context function
const useGridFunctions = () => {
const functions = useContext(FunctionalityContext);
return functions;
};
const CustomComponent = () => {
const functions = useGridFunctions();
return (
<div>
<button onClick={functions.gridItemSpecificFunction}>I am a custom component</button>
</div>
);
}
const ActiveComponent = () => {
const functions = useGridFunctions();
return (
<div>
<button onClick={functions.activeFunction}>I am a active component</button>
</div>
);
}
const ParentGrid = ({ functions }) => {
const functions = useMemo(() => functions, [functions]);
// the pass functions object to our context provider to get accesso through dom tree
return (
<FunctionalityContext.Provider value={functions}>
<Grid
CustomComponent={CustomComponent}
ActiveComponent={ActiveComponent}
/>
</FunctionalityContext.Provider>
);
}
As you can see you still keep your code almost the same, but you are adding a extra layer that will store the functionalities that will be used by components, so Context Api will help you to achieve this as you want.

Adjacent JSX elements wrapping with Fela and React issues with parent component

Adjacent JSX elements must be enclosed by a parent tag, which is causing me some issues with implementing Fela. I am new to all these technologies, and trying to ensure that I am applying them with best practices in mind. Say I have a Page component and a fela component -- pageWrapperCss :
const PageWrapperCss = createComponent(
(props) => (
{
paddingTop: props.navbarHeight + 'px',
display: 'flex',
flexDirection: 'column',
alignContent: 'stretch',
flexGrow: 1,
flexShrink: 0,
}
), 'div'
);
const Page = ({view}, {cssVars}) => {
// some logic here which may set TargetView = Dashboard component
return (
<PageWrapperCss {...cssVars} >
<TargetView />
</PageWrapperCss>
);
};
export default Page;
The TargetView component is composed of sections, which also have their css composed with Fela. Below are the sections wrapped by a parent node -- div. Therein lies the problem. Now I have a div node between my parent component -- PageWrapperCss -- which generates a div node with the attached classNames.
class Dashboard extends Component {
render() {
let { cssVars } = this.context;
return (
<div>
<SectionWrapperCss {...cssVars}>
<HeaderSection>
</SectionWrapperCss>
<SectionWrapperCss {...cssVars}>
<BodySection />
</SectionWrapperCss>
<SectionWrapperCss sectionFlex="1" {...cssVars}>
<AnotherSection />
</SectionWrapperCss>
</div>
);
}
}
Before I had PageWrapperCss component in the Dashboard component like the following, which worked fine; however I want to keep the Fela css logic within the component it relates to, so that I don't have to keep generating PageWrapperCss components in each target view.
class Dashboard extends Component {
render() {
let { cssVars } = this.context;
return (
<PageWrapperCss>
// sections
</PageWrapperCss>
);
}
}
How have others dealt with this? Fela is pretty flexible, I could just render the css in the Page component, and persist the className via props to all the views, and add them to the parent node.
class Dashboard extends Component {
render() {
let { cssVars } = this.context;
return (
<div className={this.props.pageCss} >
// sections
</div>
);
}
}
thanks!

In react, how to get noticed when children change?

I am making this class called Scrollable which enables scrolling if the width/height of the children elements exceeds a certain value. Here is the code.
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
render() {
let outter_styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={outter_styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
// To use: <Scrollable y><OtherComponent /></Scrollable>
This works great. Except now I wish to add one more functionality which makes the scrollable always scroll to the bottom. I have some idea of how to do it:
this.outterEl.scrollTop = this.innerEl.offsetHeight;
But this only need to be called when this.props.children height changes. Is there any idea on how to achieve this?
Thanks in advance.
I would recommend a package element-resize-detector. It is not React-specific but you can easily build a high-order component around it or integrate your Scrollable component with it.
Now I have an idea of solving this.
Since I am using react-redux. The problem is that I could not use lifecycle hooks on this Scrollable component since this.props.children might not necessarily be changed when the content is updated.
One way to achieve this is to make Scroll component aware of the corresponding values in the redux state. So that when that relevant value is updated, we can scroll down to the bottom.
Scrollable component:
import React, { Component } from 'react';
const INITIAL = 'initial';
class Scrollable extends Component {
componentWillUpdate(){
if(this.props.autoScroll){
// only auto scroll when the scroll is already at bottom.
this.autoScroll = this.outterEl.scrollHeight - this.outterEl.scrollTop - Number.parseInt(this.props.height) < 1;
}
}
componentDidUpdate(){
if(this.autoScroll) this.outterEl.scrollTop = this.outterEl.scrollHeight;
}
render() {
let styles = {
overflowX: (this.props.x? 'auto': INITIAL),
overflowY: (this.props.y? 'auto': INITIAL),
maxWidth: this.props.width || INITIAL,
maxHeight: this.props.height || INITIAL,
};
return (
<div ref={el => this.outterEl = el} style={styles}>
<div ref={el => this.innerEl = el}>
{this.props.children}
</div>
</div>
);
}
};
export default Scrollable;
Scrollable container:
import { connect } from 'react-redux';
import Scrollable from '../components/Scrollable';
const mapStateToProps = (state, ownProps) => Object.assign({
state: state[ownProps.autoScroll] || false
}, ownProps);
export default connect(mapStateToProps)(Scrollable)
With this, Scrollable's life cycle hooks will be called when the corresponding state changes.

Categories

Resources