Suppose we have a Container component as follows.
class Container extends React.Component {
handleClose = () => {
// need to ask 'Content' is it okay with closing
const isContentAgree = /* */;
if (isContentAgree) this.props.onClose();
};
render () {
const {content: Content} = this.props;
return (
<button onClick={this.handleClick}>Close</button>
<Content {/* some container-specific props */} />
);
}
}
Usage:
<Container content={SomeComponent}/>
In this scenario how can I pass a callback function from SomeComponent to Container? This callback is to be called when the button in the Container is clicked and returns a boolean value.
You need to keep isContentAgree in state and you can pass function to toggle isContentAgree to child component.
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
isContentAgree: false
}
}
toggleContentAgree = (e) => {
this.setState({ isContentAgree: e.target.value })
}
handleClose = () => {
// need to ask 'Content' is it okay with closing
const isContentAgree = this.state.isContentAgree;
if (isContentAgree) this.props.onClose();
};
render () {
const {content: Content} = this.props;
return (
<button onClick={this.handleClose}>Close</button>
<Content toggleContentAgree={this.toggleContentAgree} />
);
}
}
you can simply pass the callback function as prop to the component.
<Content onHide={handleClose} />
in Component you have to call props.onHide function as needed.
You can use:
React.cloneElement(SomeComponent, [props...])
And as "props" to pass a function that updates container state.
You should do it using store (Redux/Mobx/ContextAPI). That's the ideal way to do it.
But...
You can still pass the callback function:
class Container extends React.Component {
render () {
const {content: Content, callback} = this.props;
return (
<button onClick={this.handleClick}>Close</button>
<Content onSomething={callback} {/* ... */} />
);
}
}
<Container content={SomeComponent} callback={someCallbackFunction}/>
Related
I have this parent App.jsx, with two components <Child1/> and <Child2/> imported.
export default function App() {
const [isFlipped, setIsFlipped] = React.useState(false);
const handleSelectPlayers = () => {
setIsFlipped(true);
}
const handleDeselectPlayers = () => {
setIsFlipped(false);
}
return (
<Flippy
isFlipped={isFlipped}
flipDirection="horizontal" // horizontal or vertical
style={{ width: "400px", height: "600px" }} /// these are optional style, it is not necessary
>
<FrontSide>
<Child1 onSelectPlayers={handleSelectPlayers} /> // <-----
</FrontSide>
<BackSide>
<Child2 onDeselectPlayers={handleDeselectPlayers} /> // <-----
</BackSide>
</Flippy>
);
}
This is Child1.jsx, where I have 'players' set locally by this.setState():
class Child1 extends Component {
constructor(props) {
super(props);
this.state = {
players:[]
};
}
async getPlayers() {
const res = await fetch("/json/players.json");
const data = await res.json();
const players = Object.values(data.Players)
this.setState({
players: players
},() => console.log(this.state.players));
}
handlePlayers = () => {
this.props.onSelectPlayers();
};
render() {
return (
...
<Button handleClick={() => this.handlePlayers()}></Button>
...
);
And here Child2.jsx, which needs 'players' as props, given the fact they are fetched at Child1.jsx.
class Child2 extends Component {
constructor(props) {
super(props);
this.state = {
players:[]
};
}
handlePlayers = () => {
// do something with players here
};
handleChangePlayers = () => {
this.props.onDeselectPlayers();
};
render() {
return (
...
<Button handleClick={() => this.handlePlayers()}>
<Button handleClick={() => this.handleChangePlayers()}>
...
);
}
I know I can achieve this by having a callback to App.jsx at Child1.jsx, so I can pass players as props to Child2.jsx, but how so?
You can keep the players state on the Parent of both Child components. This way, you can pass it down as props to the relevant components. Refer to my comments on the code for insight
function App(){
const [players, setPlayers] = React.useState(); // single source of truth for players
return (
<React.Fragment>
<Child1 setPlayers={setPlayers}/> // pass state setter to Child1 where you perform the xhr to fetch players
<Child2 players={players}/> // pass players down as props to Child2
</React.Fragment>
)
}
class Child1 extends React.Component{
componentDidMount(){
this.getPlayers(); // sample fetching of players
}
getPlayers() {
this.props.setPlayers([ // set players state which resides on the parent component "App"
"foo",
"bar"
]);
}
render() {return "Child1"}
}
class Child2 extends React.Component{
componentDidUpdate(){
// this.props.players contains updated players
console.log(`Child2 players`, this.props.players);
}
render() {return "Child2"}
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
So I have a parent and children component.
Parent passes whatever is typed in the search bar as a prop to the children.
then the api fetch should be executed, I see the fetch object in the console. I'm having difficulties setting the children state from the parent.
Any tips would be appreciated, thank you and happing coding :D
class HelloComponent extends React.Component {
render () {
return <h1>Github API repositories </h1>;
}
}
class Parent extends React.Component {
constructor (props) {
super (props);
this.boo = this.boo.bind(this);
this.state = {path: ''};
}
boo = (event) => {
event.preventDefault();
//alert('it works!');
let url = 'https://api.github.com/search/repositories?q='+ this.state.path;
//let parameter = this.state.path;
console.log(url);
I tried using this.props or just this.response..etc
axios.get(url)
.then(response => {
console.log(response.data.items)
this.setState({
repo : this.props.response.data.items
})
})
}
//set the state from the search bar
searchQuery = (event) => {
this.setState({ path : event.target.value });
}
render() {
return(
//call Repositories component and pass the current state as props
<div>
<form onSubmit={this.boo}>
<input type="text" onChange={this.searchQuery} />
<input type="submit" value="Search"/>
</form>
<Child search= {this.state.path} />
{/* <button onClick={this.boo}>fuck</button> */}
</div>
);
}
}
class Child extends React.Component {
constructor (props) {
super (props);
//this.boo = this.boo.bind(this);
this.state = { repo : ''};
}
render () {
{/*
const titles = this.state.repo.map( (repo) =>
<tr key={ repo.id }>
<td> { repo.name }</td>
<td><a href={repo.html_url}>{repo.html_url}</a></td>
</tr>
);
return (
<div>
<table>
<tbody>
<tr><th>Name</th><th>URL</th></tr>
{titles}
</tbody>
</table>
</div>
);
*/}
return (
<h1>{this.state.repo}</h1>
);
}
}
class App extends React.Component {
render () {
return (
<div>
<HelloComponent />
<Parent />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
I think first is this part of your code is wrong
axios.get(url)
.then(response => {
console.log(response.data.items)
this.setState({
repo : response.data.items
})
})
First you need to pass prop repo to child
<Child repo={this.state.repo} search= {this.state.path} />
To set state of child from parent, you don't need to make any change in parent, Add componentWillReceiveProps method in and setState there
componentWillReceiveProps(nextProps) {
this.setState({
repo: nextProps.repo
})
}
I am attempting to return a component based on a parameter that was passed on a onClick handler this.showComponentToRender('features'). If features is clicked, it runs the showComponentToRender(name) and sets the state and in the render(), the {this.state.showComponent} shows the proper component.
However, a problem surfaces when I attempt to pass a prop resetFeatures={this.props.resetFeatures} within the
showFeatures() {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
/>);
}
Clicking on the RESET A4 link, calls the resetCrossbow() which activates a function in the parent component. The parent component updates its state, and passes the state as a prop to its child.
For some reason, I can not get the resetFeatures prop to come into the <FeaturesList /> component if I return it within a function that gets set in state. Why is this? I am looking for suggestions to fix.
If I do the traditional method of placing the <FeaturesList /> within the return of the render(), all is well.
Here's the component
import React, { Component } from 'react';
import FeaturesList from './FeaturesList';
import ColorsList from './ColorsList';
import './../assets/css/features-menu.css';
export default class FeaturesMenu extends Component {
constructor(props) {
super(props);
this.state = {
showComponent: this.showFeatures()
};
this.showFeatures = this.showFeatures.bind(this);
this.showColors = this.showColors.bind(this);
}
showFeatures() {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
/>);
}
showColors() {
this.props.resetCrossbow();
return <ColorsList switchColor={this.props.switchColor} />
}
showComponentToRender(name) {
if (name === 'features') {
this.setState({
showComponent: this.showFeatures()
});
} else {
this.setState({
showComponent: this.showColors()
})
}
}
render() {
// console.log(`this.props.resetFeatures: ${this.props.resetFeatures}`);
return (
<div id="features-menu-wrapper">
<nav id="features-menu">
<li onClick={() => this.showComponentToRender('features')}>FEATURES</li>
<li onClick={() => this.showComponentToRender('colors')}>COLORS</li>
<li onClick={() => this.props.resetCrossbow()}>RESET A4</li>
</nav>
<div id="component-wrapper">
{this.state.showComponent} // <- I am not able to pass resetFeatures prop if I do it this way. Why?
{/* <FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures} <- I am able to pass resetFeatures prop as normal.
/>
<ColorsList switchColor={this.props.switchColor} /> */}
</div>
</div>
);
}
}
The easiest way to achieve results is pass additional properties to render specific components function and use spread to pass these props to the rendered component:
const FeaturesList = ({additional = 'Empty'} = {}) => <div>Feature List with additional prop <b>{additional}</b></div>
const ColorsList = ({additional = 'Empty'} = {}) => <div>Colors List with additional prop <b>{additional}</b></div>
class FeaturesMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
showComponent: this.showFeatures({additional: 'Initial features'})
};
this.showFeatures = this.showFeatures.bind(this);
this.showColors = this.showColors.bind(this);
}
showFeatures(props) {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
{...props}
/>);
}
showColors(props) {
this.props.resetCrossbow();
return <ColorsList switchColor={this.props.switchColor} {...props} />
}
showComponentToRender(name) {
if (name === 'features') {
this.setState({
showComponent: this.showFeatures({additional: 'features adds'})
});
} else {
this.setState({
showComponent: this.showColors({additional: 'colors adds'})
})
}
}
render() {
return (
<div id="features-menu-wrapper">
<nav id="features-menu">
<li onClick={() => this.showComponentToRender('features')}>FEATURES</li>
<li onClick={() => this.showComponentToRender('colors')}>COLORS</li>
<li onClick={() => this.props.resetCrossbow()}>RESET A4</li>
</nav>
<div id="component-wrapper">
{this.state.showComponent}
</div>
</div>
);
}
}
const props = {
resetCrossbow: () => null,
switchColor: () => null,
updateCad: () => null,
resetFeatures: () => null
}
ReactDOM.render(<FeaturesMenu {...props}/>, document.querySelector('root'))
<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>
<root/>
You should not save the entire component in your state. Just set a string related to it and as the value changes, the FeaturesMenu will react and
call the function to render the correct component. Obviously, this code can be changed, but I think I've made my point. =)
const FeaturesList = props => (<div>Features List</div>);
const ColorsList = props => (<div>Colors List</div>);
class FeaturesMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
currentComponent: 'Features'
};
this.showFeatures = this.showFeatures.bind(this);
this.showColors = this.showColors.bind(this);
}
showFeatures() {
return (<FeaturesList
updateCad={this.props.updateCad}
resetFeatures={this.props.resetFeatures}
/>);
}
showColors() {
this.props.resetCrossbow();
return <ColorsList switchColor={this.props.switchColor} />
}
showComponentToRender(name) {
this.setState({ currentComponent: name })
}
render() {
return (
<div id="features-menu-wrapper">
<nav id="features-menu">
<li onClick={() => this.showComponentToRender('Features')}>FEATURES</li>
<li onClick={() => this.showComponentToRender('Colors')}>COLORS</li>
<li onClick={() => this.props.resetCrossbow()}>RESET A4</li>
</nav>
<div id="component-wrapper">
{this[`show${this.state.currentComponent}`]()}
</div>
</div>
);
}
}
// for testing purposes
const props = {
resetCrossbow: () => {},
switchColor: () => {},
updateCad: () => {},
resetFeatures: () => {}
}
ReactDOM.render(<FeaturesMenu {...props}/>, document.querySelector('main'))
<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>
<main/>
I have to call some methods from component B inside component A.
Here is the component B: (In the case that you need more code just request me and I will update my post)
import A from "./A";
export default class B extends Component {
componentDidUpdate (prevProps, prevState) {
this.props.onRef(this);
}
renderContent ( ) {
let toolbarActiveItem = Toolbar.getActiveItem();
if (Toolbar.getActiveItem()=="XXXX") {
this.refs.A._showModalHistory();
} else {
toolbarActiveItem = Toolbar.getActiveItem();
}
return (
<Container>
{(toolbarActiveItem == 'YYY') && <C navigation={this.props.navigation} cb={this.childCallback} info={this.props.navigation.state.params}/> }
</Container>
);
}
render () {
return (
<StyleProvider style={getTheme(Config.theme)}>
<A ref={component => { this.A = component; }} />
<Container ref={this.ref}>
{ this.renderNavbar(this.title) }
{ this.renderToolbar() }
{ this.renderContent() }
{ this.renderDialog() }
</Container>
</StyleProvider>
);
}
Here is the component A:
export default class A extends Component {
constructor(props) {
super();
this.subscription = 0;
this.state = {};
changePage = props.cb;
this.state = {
isModalVisibleHistory: false,
};
}
_showModalHistory = () => this.setState({ isModalVisibleHistory: true });
render() {
return (
<Modal isVisible={this.state.isModalVisibleHistory}>
<View style={styles.BG}>
<ParkHistory></ParkHistory>
</View>
</Modal>
);
}
}
My problem is that, I need to execute this.refs.A._showModalHistory(); in component B but, I see the following error:
React.Children.only expected to receive a single React element child.
I think, I have a problem to deal with rendering multiple components and ref them. I should mention that, when I delete <A ref={component => { this.A = component; }} /> my code works fine but, without calling the method. Could you please help me to solve this issue?
The problem is in the way you are accessing the ref. You have assigned ref to Component A. You are using the callback style to assign ref and so you need not write this.ref.A. Change it to this.A._showModalHistory();
Aslo the way you are setting ref to Container component is incorrect, you need to use callback there
<Container ref={(ref)=> this.ref = ref}>
One other thing is that the StyleProvider component expects a single child element. You should wrap your Container and A component in View
render () {
return (
<StyleProvider style={getTheme(Config.theme)}>
<View>
<A ref={component => { this.A = component; }} />
<Container ref={(ref)=> this.ref = ref}>
{ this.renderNavbar(this.title) }
{ this.renderToolbar() }
{ this.renderContent() }
{ this.renderDialog() }
</Container>
</View>
</StyleProvider>
);
}
class App extends Component {
constructor(props) {
super(props);
this.state = { Card: Card }
}
HandleEvent = (props) => {
this.SetState({Card: Card.Active}
}
render() {
return (
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
)
}
}
const Card = props => {
return (
<div style={props.state.Card} onClick={
props.HandleEvent}>Example</div>
)
}
Every time I click on one of the cards all of my elements change states, how do I program this to only change card that I clicked?
Here's a working example
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
0: false,
1: false
};
}
handleEvent(idx) {
const val = !this.state[idx];
this.setState({[idx]: val});
}
render() {
return (
<div>
<Card state={this.state[0]} handleEvent={()=>this.handleEvent(0) } />
<Card state={this.state[1]} handleEvent={()=>this.handleEvent(1) } />
</div>
);
}
}
const Card = (props) => {
return (<div onClick={() => props.handleEvent()}>state: {props.state.toString()}</div>);
}
You can also see it in action here
Obviously this is a contrived example, based on your code, in real world application you wouldn't store hardcoded state like {1: true, 2: false}, but it shows the concept
It's not completely clear from the example what is the Card in the constructor. But here the example of how you can modify clicked element.
Basically you can keep only index of clicked element in parent's state, and then pass it as some property to child component, i.e. isActive here:
const cards = [...arrayOfCards];
class App extends Component {
constructor(props) {
super(props);
this.state = { activeCardIndex: undefined }
}
HandleEvent = (index) => {
this.SetState({
activeCardIndex: index
});
}
render() {
return ({
// cards must be iterable
cards.map((card, index) => {
return (
<Card
key={index}
Card={Card}
isActive={i === this.state.activeCardIndex}
HandleEvent={this.HandleEvent.bind(this, index)}
/>
);
})
});
}
}
const Card = props => {
// style active card
const style = Object.assign({}, props.Card, {
backgroundColor: props.isActive ? 'orange' : 'white',
});
return (
<div style={style} onClick={
props.HandleEvent}>Example</div>
)
}