Pass props with callback from Parent to component - javascript

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>

Related

How can I make a list of integers click on element with corresponding id?

I have a list of ids (integer) and I have multiple components.
After a request to my API, the component receives a list of ids that should already be active.
I want to simulate a click on each element with the same id as the one in my array. I know I can use refs to do that, but I don't undertstand how to make it works with a list of elements.
Here's my code :
import React, { Component } from 'react'
import InterestBox from './InterestBox'
import Axios from 'axios'
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList
import React, { Component } from 'react'
export class InterestBox extends Component {
constructor(props) {
super(props);
this.images = require('../../img/interests/*.svg');
this.state = {activated: false};
this.interest_box_content = React.createRef();
this.interest_text = React.createRef();
this.handleClick = this.handleClick.bind(this);
this.updateDimensions = this.updateDimensions.bind(this);
}
handleClick() {
this.props.handleClick(this.props.id, this.props.title);
this.setState(prevState => ({
activated: !prevState.activated
}))
}
updateDimensions() {
console.log((window.getComputedStyle(this.refs.interest_box_content).width))
this.refs.interest_text = (window.getComputedStyle(this.refs.interest_box_content).width)
}
render() {
return (
<div className="column is-one-fifth-desktop is-half-touch">
<div className="interest-box">
<div className="interest-box-adjuster">
<div ref={"interest_box_content"} className={"interest-box-content " + (this.state.activated == true ? 'interest-box-activated' : '')} onClick={this.handleClick}>
<img className="interest-icon" src={this.images[this.props.icon]} style={{'height': '50%'}}></img>
<i className="activated-icon fas fa-check"></i>
<span ref={"interest_text"} className="interest-text">{this.props.title}</span>
</div>
</div>
</div>
</div>
)
}
}
export default InterestBox
In the InterestList "componentDidUpdate" method, the value of the item is an integer.
I want to use this integer to "click" on the InterestBox with the corresponding "id".
How can I achieve this ?
You can store an array of elements in one ref, like this:
constructor(props) {
super(props);
this.state = {pinterests: []}
this.pinterestRefs = React.createRef()
}
...
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} ref={pinterestRef => this.refs.pinterestRefs.push(pinterestRef)} />
})}
</React.Fragment>
)
}
and then call the click function on each in a componentDidMount function:
componentDidMount() {
if (this.refs.pinterestRefs.length) {
this.refs.pinterestRefs.forEach(pinterestEl => {
pinterestEl.click();
});
}
}
Since this.pinterestRefs is a ref and not an array, the push method is not available. Unfortunately, we do not have a definite length so we can't declare the refs preemptively. However, we can add it to this.refs object and the convert it to an array:
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(Object.values(this.refs)); // Array with all refs
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
{/*I'm assuming each item has a unique id, if not, create one*/}
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} ref={pinterest.id} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList;

Why can't I add any value to the array in state?

I have a lot of hits, which I want to add to an array once a hit is pressed. However, as far as I observed, the array looked like it got the name of the hit, which is the value. The value was gone in like half second.
I have tried the methods like building constructor, and doing things like
onClick={e => this.handleSelect(e)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
defaultValue={hit.name}
and so on
export default class Tagsearch extends Component {
constructor(props) {
super(props);
this.state = {
dropDownOpen:false,
text:"",
tags:[]
};
this.handleRemoveItem = this.handleRemoveItem.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
}
handleSelect = (e) => {
this.setState(
{ tags:[...this.state.tags, e.target.value]
});
}
render() {
const HitComponent = ({ hit }) => {
return (
<div className="infos">
<button
className="d-inline-flex p-2"
onClick={e => this.handleSelect(e)}
value={hit.name}
>
<Highlight attribute="name" hit={hit} />
</button>
</div>
);
}
const MyHits = connectHits(({ hits }) => {
const hs = hits.map(hit => <HitComponent key={hit.objectID} hit={hit}/>);
return <div id="hits">{hs}</div>;
})
return (
<InstantSearch
appId="JZR96HCCHL"
apiKey="b6fb26478563473aa77c0930824eb913"
indexName="tags"
>
<CustomSearchBox />
{result}
</InstantSearch>
)
}
}
Basically, what I want is to pass the name of the hit component to handleSelect method once the corresponding button is pressed.
You can simply pass the hit.name value into the arrow function.
Full working code example (simple paste into codesandbox.io):
import React from "react";
import ReactDOM from "react-dom";
const HitComponent = ({ hit, handleSelect }) => {
return <button onClick={() => handleSelect(hit)}>{hit.name}</button>;
};
class Tagsearch extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: []
};
}
handleSelect = value => {
this.setState(prevState => {
return { tags: [...prevState.tags, value] };
});
};
render() {
const hitList = this.props.hitList;
return hitList.map(hit => (
<HitComponent key={hit.id} hit={hit} handleSelect={this.handleSelect} />
));
}
}
function App() {
return (
<div className="App">
<Tagsearch
hitList={[
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
]}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
additionally:
note the use of prevState! This is a best practice when modifying state. You can google as to why!
you should define the HitComponent component outside of the render method. it doesn't need to be redefined each time the component is rendered!

React Native - How to append to parent state array while in child component (StackNavigators)?

My project is looping through a data array in a child component Main, and I'm trying to update the state in parent component, App, on an event (swiping right on a card in Main), so that I could access the data that was 'swiped right' on a sibling Component in Favorites. Hopefully that makes sense?
The project structure is as such:
App
|__ Rootstack
|
|__Favorites
|__Main
In my Main component, I am mapping the collection array and looping thru:
collection = imagedata;
// a local JSON array of data that I am looping thru in Main
class Main extends React.Component {
_toFavs = () => {
this.props.navigation.navigate('Favorites');
};
render() {
const contents = collection.map((item, index) => {
return (
<Card key={index}>
......
</Card>
)
});
return (
<View>
<CardStack
onSwiped={() => {console.log('onSwiped')}
onSwipedRight={() => console.log('onSwipedLeft')}>
//
//HERE IS THE PART - HOW TO UPDATE THE 'favoritesList' array in the parent 'App's state?
//
{contents}
</CardStack>
</View>
);
}
}
const RootStack = StackNavigator(
{
Main: {
screen: Main},
Favorites: {
screen: Favorites}
},
{
initialRouteName: 'Main'
}
);
class Favorites extends React.Component {
// The plan is to eventually access the favoritesList array in App's state here and display cards that were swiped right in the Main component.
_onPress = () => {
this.props.navigation.navigate('Main');
};
render() {
return (
<View><Text>Hello!</Text></View>
);
}
}
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
favoritesList: []
};
}
render() {
return <RootStack />;
}
}
I've come across some other answers of updating state such as
this.setState({ favoritesList: [...this.state.favoritesList, 'new value'] }), but how can I do this to the .state of App while i'm inside a child component Main?
Thanks in advance!
collection = imagedata;
// a local JSON array of data that I am looping thru in Main
class Main extends React.Component {
_toFavs = () => {
this.props.navigation.navigate('Favorites');
};
render() {
const contents = collection.map((item, index) => {
return (
<Card key={index}>
......
</Card>
)
});
return (
<View>
<CardStack
onSwiped={() => {console.log('onSwiped')}
onSwipedRight={() => {console.log('onSwipedLeft') ;
this.props.screenProps()}}>
//
{contents}
</CardStack>
</View>
);
}
}
const RootStack = StackNavigator(
{
Main: {
screen: Main},
Favorites: {
screen: Favorites}
},
{
initialRouteName: 'Main'
}
);
class Favorites extends React.Component {
// The plan is to eventually access the favoritesList array in App's state here and display cards that were swiped right in the Main component.
_onPress = () => {
this.props.navigation.navigate('Main');
};
render() {
return (
<View><Text>Hello!</Text></View>
);
}
}
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
favoritesList: []
};
}
updateArr=()=>{consol.log("fire") }
render() {
return <RootStack screenProps={this.updateArr} />;
}
}
i hope it solve your problem
update props-name

How to pass a prop to a component that is returned based on state?

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/>

How to create shared-singleton components across all the platform?

I'm thinking on creating a React component called LoadingMask, where I can show or not (depending on the state) a loading mask from any component. The idea is showing it before an ajax call, and hiding it after I receive the data.
I don't want to display two masks at the same time, so if one component is making a request, and another one creates another request, I want to add 1 to my "MaskCounter", and substract one when the Request is finished. If the counter is 0, I need to hide the LoadingMask.
I order to do this, I think I need to create a "Singleton" component, that I can share through the whole platform, so there's only exist one LoadingMask. I also don't think it's nice to send the events to hide/show the mask to all components.
Any ideas?
To share data between components, you can :
Use a lib like Redux, and keep in shared store your mask loader status
Use the React context api from your root component, and share loader status to all childrens. See an example below :
class Application extends React.Component {
constructor() {
super();
this.state = {
nbTasks: 0
};
this.addTask = this.addTask.bind(this);
this.removeTask = this.removeTask.bind(this);
this.isLoading = this.isLoading.bind(this);
}
addTask() {
this.setState(prevState => ({
nbTasks: prevState.nbTasks + 1
}));
}
removeTask() {
this.setState(prevState => ({
nbTasks: prevState.nbTasks - 1
}));
}
isLoading() {
return this.state.nbTasks > 0;
}
getChildContext() {
return {
addTask: this.addTask,
removeTask: this.removeTask,
isLoading: this.isLoading
};
}
render() {
return (
<div>
<ComponentX />
<ComponentY />
<LoadingMask />
</div>
);
}
}
Application.childContextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func,
isLoading: PropTypes.func
};
const LoadingMask = (props, context) => (
context.isLoading()
? <div>LOADING ...</div>
: null
);
LoadingMask.contextTypes = {
isLoading: PropTypes.func
};
class ComponentX extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
message: 'Processing ...'
};
}
componentDidMount() {
this.context.addTask();
setTimeout(() => {
this.setState({
message: 'ComponentX ready !'
});
this.context.removeTask();
}, 3500);
}
render() {
return (
<div>
<button disabled>{this.state.message}</button>
</div>
);
}
}
ComponentX.contextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func
};
class ComponentY extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
message: 'Processing ...'
};
}
componentDidMount() {
this.context.addTask();
setTimeout(() => {
this.setState({
message: 'ComponentY ready !'
});
this.context.removeTask();
}, 6000);
}
render() {
return (
<div>
<button disabled>{this.state.message}</button>
</div>
);
}
}
ComponentY.contextTypes = {
addTask: PropTypes.func,
removeTask: PropTypes.func
};
ReactDOM.render(
<Application />,
document.getElementById('app')
);
<script src="https://unpkg.com/prop-types/prop-types.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="app"></app>
I found this library use-between to be simple, powerful and useful. It removes complexity of redux for sharing data between within functional components.
import React, { useState, useCallback } from 'react';
import { useBetween } from 'use-between';
Context/Session.ts
export const useShareableState = () => {
const [count, setCount] = useState(0);
const inc = useCallback(() => setCount(c => c + 1), []);
const dec = useCallback(() => setCount(c => c - 1), []);
return {
count,
inc,
dec
};
};
App.tsx
import { useBetween } from 'use-between';
import { useShareableState } from './src/Context/Session'
const useSharedCounter = () => useBetween(useShareableState);
const Count = () => {
const { count } = useSharedCounter();
return <p>{count}</p>;
};
const Buttons = () => {
const { inc, dec } = useSharedCounter();
return (
<>
<button onClick={inc}>+</button>
<button onClick={dec}>-</button>
</>
);
};
const App = () => (
<>
<Count />
<Buttons />
<Count />
<Buttons />
</>
);
export default App;

Categories

Resources