Passing function to this.props.children - javascript

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>
)
}
}

Related

setState doesn't work in onSubmit function

In this component, the setState() doesn't work after firing an async onSubmit function.
constructor(props) {
super(props);
this.state = {
addingProduct: false
}
this.onSubmit = this.onSubmit.bind(this);
}
async onSubmit(formData) {
const { addProductToServer } = this.props;
this.setState({addingProduct: true});
await addProductToServer(formData);
//this doesn't set state to false
this.setState({addingProduct: false})
}
I don't think it's a good idea to destructure your props inside a function, that should come inside the render method. This is my attempt to simulate what you're trying to do.
import React, { Component } from "react";
const name = "Mathew";
export default class StackOverflow extends Component {
constructor(props) {
super(props);
this.state = {
addingProduct: false,
server: []
};
this.addProductToServer = this.addProductToServer.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
addProductToServer(data) {
return this.state.server.push(data);
}
async onSubmit(formData) {
this.setState({ addingProduct: true });
await this.addProductToServer(formData);
this.setState({ addingProduct: false });
}
render() {
// const { addProductToServer } = this.props
console.log("server", this.state.server);
console.log("addingProduct", this.state.addingProduct)
return (
<div>
<h3>Hello StackOverflow</h3>
<p>{this.state.server.map((item, index) => (
<li key = {index}>{item}</li>
))}</p>
<button onClick={() => this.onSubmit(name)} type="submit">
Submit
</button>
</div>
);
}
}
This works. And this is it in a sandbox

React ScrollIntoView not working from parent component

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.

Not showing any text when clicked on the button in reactjs

I am trying to implement in toggle button feature where when clicking on button willshowtext and clicking on button again willhidetext.
When i tried implement this i am stuck at displaying the text . I used the below for showing the text
import React, { Component } from "react";
export default class DisplayStats extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
console.log('Click happened');
<div>HELLO</div>
}
render() {
return (
<div className="container">
<h1>This is the stats.</h1>
<button onClick={this.handleClick}>Click Me</button>
</div>
)
}
}
With this i can see the console.log is created but i cant able to see the HELLO when i clicked on the button.
Am i missing anything here ?
Any help is appreciated
Thanks
You cannot return an element from an event handler and have it render like that.
You need to hide the text behind a flag and then toggle that flag.
First we create a flag in state. This defines if the toggle text should be displayed.
this.state = {
showText: false // Should the text be displayed?
};
Next we update the click handler to toggle that state flag.
this.setState((state) => ({
showText: !state.showText // Toggle showText
}))
Finally we conditionally render the toggle text. If showText is true, then render the text, if it is false do not render it.
{this.state.showText && <div>HELLO</div>}
Optional:
As pointed out by Mosè Raguzzini you do not need to bind your event handler.
this.handleClick = this.handleClick.bind(this); // This is not needed
handleClick = () => {} // because this is an arrow function
All together now:
import React, { Component } from "react";
export default class DisplayStats extends Component {
constructor(props) {
super(props);
this.state = {
showText: false // Should the text be displayed?
};
}
handleClick = () => {
console.log('Click happened');
this.setState((state) => ({
showText: !state.showText // Toggle showText
}))
}
render() {
return (
<div className="container">
<h1>This is the stats.</h1>
{this.state.showText && <div>HELLO</div>}
<button onClick={this.handleClick}>Click Me</button>
</div>
)
}
}
You should change state on toggle.
import React, { Component } from "react";
export default class DisplayStats extends Component {
state = {
isToggled : false
}
constructor(props) {
super(props);
}
handleClick = () => {
console.log('Click happened');
this.setState({isToggled : !this.state.isToggled});
}
render() {
return (
<div className="container">
<h1>This is the stats.</h1>
<button onClick={this.handleClick}>Click Me</button>
</div>
{(() => {
if(this.state.isToggled){
return <div>HELLO</div>
}
else{
return <div></div>
}
})()}
)
}
}
You do not need to use bind if you already use arrow functions, beside this, you have to learn how to manage state:
import React, { Component } from "react";
export default class DisplayStats extends Component {
constructor(props) {
super(props);
this.state = {
displayedText: '',
}
}
handleClick = () => {
console.log('Click happened');
this.setState({ displayedText: 'This is my text.'});
}
render() {
return (
<div className="container">
<h1>This is the stats. {this.state.displayedText}</h1>
<button onClick={this.handleClick}>Click Me</button>
</div>
)
}
}
To achieve this, you'll want to track state in your component to determine if the text should be displayed or not. The following code should achieve what you're after:
export default class DisplayStats extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
console.log('Click happened');
// When the button is clicked, the text display state is toggled
this.setState({ showText : !this.state.showText })
}
render() {
// Extract state to determine if the text should be shown
const showText = !!this.state.showText
return (
<div className="container">
{ /* Render div with text is showState is truthy /* }
{ showText && <div>HELLO</div> }
<h1>This is the stats.</h1>
<button onClick={this.handleClick}>Click Me</button>
</div>
)
}
}
That is not how react and other state based frameworks work. The idea is that the view should change when the state changes and only state can cause any change in the view. What you would need to do is on click of button, change the state which in turn will cause your view to update
import React, { Component } from "react";
export default class DisplayStats extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
visible: false
}
}
handleClick = () => {
this.setState({visible: !this.state.visible});
}
render() {
return (
<div className="container">
<h1>This is the stats.</h1>
<button onClick={this.handleClick}>Click Me</button>
{ this.state.visible ? <div>Hello</div> : '' }
</div>
)
}
}

React bootstrap duplicated modal components

For some reason the modal is rendered more than one time, sometimes 2 or 3. Then, after a few seconds the aditionals modals are removed automatically.
The modal is opened by a route so I'm doing somethig like this:
const ModalWrapper = (props) => {
return (
<Modal
show={props.showModal}
onHide={props.hide}
>
...
</Modal>
);
};
class ComponentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: true,
};
}
#autobind
closeModal() {
this.props.history.goBack();
this.setState({showModal: false});
}
render() {
return (
<ModalWrapper
{...this.state}
hide={this.closeModal}
/>
);
}
}
Usually this happens when the Modal is inside a wrapper, for example:
const ChildrenWrapper = ({children}) => {
return <div>{children}</div>
}
And the modal is a child in the wrapper:
const ModalWrapped= ({}) => {
return <ChildrenWrapper>
<Modal show={true}>some content</Modal>
</ChildrenWrapper>
}
Than the App :
const App = () => {
return <ModalWrapped/>
}
The result is that the instance of the Modal is rendered 2 times in the virtual dom.
I got the same issue and i opened an issue on GitHub:
https://github.com/react-bootstrap/react-bootstrap/issues/2730

How to trigger page refresh from another component in ReactJS?

I am newbie to React. In my CRUD aplication I have a Main component. Then in the List Component I make an API call to load item from server. The problem is that after a new item submitting in Create component I need to refresh my List in order to see a new item added. What is the possible non-flux solution?
Main.jsx
export default class Main extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
showModal: false,
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({showModal: true});
}
closeModal() {
this.setState({showModal: false});
}
render() {
return (
<div>
<div class="navbar">
<div class="nav-item"onClick={this.openModal}>Create</div>
</div>
<div class="modal" show={this.state.showModal} onHide={this.closeModal}>
<div class="modal-body">
<Create onItemCreate={this.closeModal}/>
</div>
</div>
<List />
</div>
);
}
}
UPDATE:
List.jsx
export default class List extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
items: []
};
}
refreshList() {
$.ajax({
url : apiPrefix,
dataType : 'json',
type : 'GET',
success: data => {
this.setState({items: data.items});
},
error: (xhr, status, err) => {
console.error(apiPrefix, status, err.toString());
}
});
}
render() {
if( this.state.findings === undefined ) {
return <div>Loading...</div>
} else {
return(
<div>
<div>
{
this.state.findings.map((item) => {
return <Item key={item._id} item={item} />
})
}
</div>
</div>
);
}
}
componentWillMount() {
this.refreshList();
}
}
Create.jsx
export default class Create extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
};
}
// Perform a post request to save a formData
onSubmit({formData}) {
formData.type = this.state.selectValue;
axios.post(apiPrefix, formData)
.then(() => {
this.closeModal();
// THIS LIST NEED TO BE REFRESHED
});
}
closeModal() {
this.props.onFindingCreated();
}
render() {
return (
<div>
<div class="form"></div>
</div>
)
}
}
when your control reaches to Create page you can check it conditionally in render as:-
Create a variable that will store your state initially when your create page will be loaded first
render{
if(var==null) //will load your var when this class will run first
{
var=this.props.onItemCreate.showModal
return()..
}
else if(this.props.onItemCreate.showModal!=var and var!=null)
{
var=[] //empty variable and update it to new props
var=this.props.onItemCreate.showModal
}
}
Or
you can use
componentWillReceiveProps in your child class and check if props has been updated then simply set your state to that props and hence your list also

Categories