I have a function called addFunc in my main Class. This class calls the RenderItem function to display a list of items. Each item has an onClick that should execute the addFunc function.
I am unable to call the addFunc function from within my RenderItem function because they are in different components. How do I get past this?
This is a summary of my code:
const selectedData = []
class Search extends Component {
constructor(props) {
super(props);
this.addFunc = this.addFunc.bind(this);
}
addFunc(resultdata){
console.log(resultdata)
selectedData = [...selectedData, resultdata]
console.log(selectedData)
};
render() {
return (
<ReactiveList
componentId="results"
dataField="_score"
pagination={true}
react={{
and: ["system", "grouping", "unit", "search"]
}}
size={10}
noResults="No results were found..."
renderItem={RenderItem}
/>
);
const RenderItem = (res, addFunc) => {
let { unit, title, system, score, proposed, id } = {
title: "maker_tag_name",
proposed: "proposed_standard_format",
unit: "units",
system: "system",
score: "_score",
id: "_id"
};
const resultdata = {id, title, system, unit, score, proposed}
return (
<Button
shape="circle"
icon={<CheckOutlined />}
style={{ marginRight: "5px" }}
onClick={this.addFunc()}
/>
);
}
You can wrap RenderItem component with another component and then render it,
const Wrapper = cb => {
return (res, triggerClickAnalytics) => (
<RenderItem
res={res}
triggerClickAnalytics={triggerClickAnalytics}
addFunc={cb}
/>
);
};
and renderItem of ReactiveList would be: renderItem={Wrapper(this.addFunc)}
then RenderItem component would be
const RenderItem = ({ res, triggerClickAnalytics, addFunc }) => {
...
see sandbox: https://codesandbox.io/s/autumn-paper-337qz?fontsize=14&hidenavigation=1&theme=dark
You can pass a callback function defined in the parent as a prop to the child component
class Parent extends React.Component {
sayHello(name) => {
console.log("Hello " + name)
}
render() {
return <Child1 parentCallback = {this.sayHello}/>
}
}
and then call it from the child component
class Child1 extends React.Component{
componentDidMount() {
this.props.parentCallback("Foo")
}
render() {
return <span>child component</span>
}
};
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>
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!
I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
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
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>
)
}