React ScrollIntoView not working from parent component - javascript

I have a parent component that has a button. When that button is clicked, it should scroll to a grid in the child component, but it is not working. There hasn't been any errors. This is what I have in the parent component:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
someState: undefined,
};
this.ref_grid = React.createRef();
}
handleClick = () => {
this.setState({
someState: newState,
}, () =>
{
if (this.ref_grid.current !== null) {
this.ref_grid.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}
);
}
render() {
return (
<>
<Button
variant="contained"
color="secondary"
size="small"
onClick={() => this.handleClick()}
>Click me!</Button>
<ChildComponent
forwardRef={this.ref_grid}
/>
</>
);
}
}
In the child component I have the ref:
class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
render() {
const {
classes
} = this.props;
return (
<Grid container spacing={3} ref={this.props.forwardRef}>
</Grid>
)
}
}
I am new to React, so I am not sure if this is the right approach. Would appreciate if anyone has any idea or example how to solve this.
Thank you in advance.

Related

Passing function to this.props.children

It seems just recently React won't treat this.props.children as a function as it did in the past.
I just coded a Modal component where its closeModal function should be passed to the children,
render() {
return (
<Modal>
{
(closeModal) => {
return (<span onClick={closeModal}>Click to close modal</span>)
}
}
</Modal>
)
}
Modal looks like this
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children }
</div>
)
}
}
I tried to pass function as a prop via this.props.children({ closeModal: this.closeModal }), guess what, this.props.children is not a function according to latest React 16.9.
As a reference for the folks working with GraphQL, I see Apollo client's <Mutation> and <Query> working quite much the same way.
How can it be achieved?
Edit: Why not a duplicate?
Because other answers rely on this.props.children as function whereas recently React is now rendering error demanding a new approach to this issue:
TypeError: this.props.children is not a function
I've answered the updates needed to show what is wrong and how it can be changed in-line below.
class Modal extends React.Component {
constructor(props) {
// 1️⃣ Make sure to make `props` available in this component.
// This call is what makes `this.props` call to be available within `Modal`.
super(props);
this.state = { show: true };
// 2️⃣ Assign a newly bound method to the matching class method
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false });
}
render() {
if (!this.state.show) return null;
// 3️⃣ Pass the modal handler to children
// If you had kept `this.close`, then when you pass the handler
// "{ closeModal: this.close }" instead of "{ closeModal: this.closeModal }"
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{/* 4️⃣ destructure `closeModal` */}
{({ closeModal }) => {
return <button onClick={closeModal}>Click to close modal</button>;
}}
</Modal>
</div>
);
}
As Emile Bergeron has kindly pointed out, you can pass this.props.children(this.close) instead of an object but I found it easier to read/use.
You can fork and try or run the code snippet below~
Thanks Emile Bergeron for the suggestion in the comment~
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = { show: true };
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false }, () => {
console.log(`Modal closed`);
});
}
render() {
if (!this.state.show) return null;
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{({ closeModal }) => {
return (
<button type="button" onClick={closeModal}>
Click to close modal
</button>
);
}}
</Modal>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
try this one
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children(this.close) }
</div>
)
}
}

REACT: Unable to update children props

I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.
I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.
Thank you!
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected }, () => {
this.sectionRef.current.changeSection(this.state.section)
})
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<Layout>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</Layout>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { this.props.selectSection("projects")}}>
{" "}
Projects
</span>
</header>
)
There seemed to be a couple of issues with your example code:
Missing closing div in Header
Using this.props instead of props in onclick in span in Header
The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected })
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<div>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</div>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { props.selectSection("projects")}}>
{" "}
Projects
</span>
</div>
</header>
)
ReactDOM.render(
<IndexPage />,
document.getElementsByTagName('body')[0]
);
.visible {
opacity: 1
}
.invisible {
opacity: 0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
There is a markup error in your code in Header component - div tag is not closed.
Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.
As #Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})
Any way here is a working example of what you trying to achieve:
// #flow
import * as React from "react";
import Header from "./Header";
type
Properties = {};
type
State = {
section: string,
headerVisible: boolean,
};
class IndexPage extends React.Component<Properties, State> {
static defaultProps = {};
state = {};
constructor(props) {
super(props);
this.state = {
section: "",
headerVisible: true,
};
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
setTimeout(
() => this.setState(
prevState => ({
section: sectionSelected,
headerVisible: !prevState.headerVisible
}),
() => console.log("Debug log: \n", this.state)
),
325
);
}
render(): React.Node {
const {section, headerVisible} = this.state;
return (
<React.Fragment>
<Header selectSection={this.displaySection} headerVisible={headerVisible} />
<br/>
<div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
</React.Fragment>
)
}
}
export default IndexPage;
and Header component
// #flow
import * as React from "react";
type Properties = {
headerVisible: boolean,
selectSection: (section: string) => void
};
const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
const headerRef = React.useRef(null);
return (
<React.Fragment>
<header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => selectSection("projects")}>Projects</span>
</div>
</header>
<br/>
<div>Header class name: {headerRef.current && headerRef.current.className}</div>
</React.Fragment>
);
};
export default ComponentName;

Updating Context from a Nested Component is not Working

I am new to react, I am trying to pass theme string and and toggleTheme function from parent to child using Context API in react.I am practicing example from React Doc with little modification https://reactjs.org/docs/context.html
my code is as following:
import React from 'react';
const ThemeContext = React.createContext({
theme: 'light',
toggleTheme: () => {}
})
class MouseTracker2 extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === 'dark'
? 'light'
: 'dark',
}));
};
this.state={
theme: 'dark',
toggleTheme: this.toggleTheme
}
}
render() {
return (
<div>
<ThemeContext.Provider value={this.state}>
<Abc />
</ThemeContext.Provider>
</div>
)
}
}
class Abc extends React.Component {
render() {
return (
<div>
<ThemeContext.Consumer>
{({theme,toggleTheme}) => {return(<Def theme={theme} onClick=
{toggleTheme} />)}}
</ThemeContext.Consumer>
</div>
)
}
}
class Def extends React.Component {
render() {
return (
<div>
<p>efgh</p>
<div>{this.props.theme}</div>
</div>
)
}
}
export default MouseTracker2
In above Code, Context is passing string from parent to child properly. However, it is not passing function from parent to child.
Thanks in Advance :)
The toggleTheme function is passed on to Def by the name onClick and hence this.props.toggleTheme is unavailable and can be accessed by this.props.onClick
class MouseTracker2 extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme: state.theme === "dark" ? "light" : "dark"
}));
};
this.state = {
theme: "dark",
toggleTheme: this.toggleTheme
};
}
render() {
return (
<div>
<ThemeContext.Provider value={this.state}>
<Abc />
</ThemeContext.Provider>
</div>
);
}
}
class Abc extends React.Component {
render() {
return (
<div>
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => {
return <Def theme={theme} onClick={toggleTheme} />;
}}
</ThemeContext.Consumer>
</div>
);
}
}
class Def extends React.Component {
render() {
return (
<div>
<p>efgh</p>
<div>{this.props.theme}</div>
<button onClick={this.props.onClick}>Toggle</button>
</div>
);
}
}
Working Codesandbox

React Handle interaction state between components

I have a simple component who show element onClick:
class MyComponent extends React.Component {
state = {
isVisible : false
}
render() {
const { isVisble } = this.state
return(
<div>
{isVisble ?
<div onClick={() => this.setState({isVisble: false})}>Hide</div> :
<div onClick={() => this.setState({isVisble: true})}>Show</div>}
</div>
)
}
}
I use this component three times in other component :
class MySuperComponent extends React.Component {
render() {
return(
<div>
<MyComponent />
<MyComponent />
<MyComponent />
</div>
)}
}
I need to pass isVisible at false for all other component if one of have isVisible to true
How to do that ?
Thanks
You should have your component controlled, so move isVisble to props and and then assign it from MySuperComponent.
Also pass MyComponent a callback so it can inform the parent if it wants to change the state.
You'd want some data structure to store that states.
https://codepen.io/mazhuravlev/pen/qxRGzE
class MySuperComponent extends React.Component {
constructor(props) {
super(props);
this.state = {children: [true, true, true]};
this.toggle = this.toggle.bind(this);
}
render() {
return (
<div>
{this.state.children.map((v, i) => <MyComponent visible={v} toggle={() => this.toggle(i)}/>)}
</div>
)
}
toggle(index) {
this.setState({children: this.state.children.map((v, i) => i !== index)});
}
}
class MyComponent extends React.Component {
render() {
const text = this.props.visible ? 'visible' : 'hidden';
return (<div onClick={this.props.toggle}>{text}</div>);
}
}
React.render(<MySuperComponent/>, document.getElementById('app'));
You can check your code here, is this what you want.
example

React toggle component

I have this simple code below. When I press the Toggle Button the component Child should hide/show, but it's not.
Do I have to re-render something?
I don't want to switch in/out a CSS class, just toggle via a button click
import React, {Component} from 'react';
let active = true
const handleClick = () => {
active = !active
}
class Parent extends React.Component {
render() {
return (
<div>
<OtherComponent />
{active && <Child />}
<button type="button" onClick={handleClick}>
Toggle
</button>
</div>
)
}
}
class Child extends React.Component {
render() {
return (
<div>
I am the child
</div>
)
}
}
class OtherComponent extends React.Component {
render() {
return (
<div>
I am the OtherComponent
</div>
)
}
}
You need to get or set it via state:
class Parent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
active: true,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
active: !this.state.active
});
}
render() {
return (
<div>
<OtherComponent />
{this.state.active && <Child />}
<button type="button" onClick={this.handleClick}>
Toggle
</button>
</div>
)
}
}
Note that with this approach you will re:render the entire parent component (as well as it's children).
Consider using another approach, when you are passing a prop to the child component and it will render itself with content based on this prop (it can render an empty div or something).
There are number of libraries that make this job easy for you, like react-collapse with animations and stuff.
You should only use state and props to manage your app state.
So instead try:
class Parent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
active: true
};
this.handleClick = this.handleClick.bind(this);
}
const handleClick = () => {
this.setState({active = !this.state.active});
}
render() {
return (
<div>
<OtherComponent />
{this.state.active && <Child />}
<button type="button" onClick={handleClick}>
Toggle
</button>
</div>
);
}
}
Alernatively, you could use forceUpdate() to force a re-render, but this is strongly discouraged:
const handleClick = () => {
active = !active;
this.forceUpdate();
}

Categories