React Native pass a function as props to child component - reactjs

I am trying to pass a function from one component to its child, in this case Posts.js maps through each post and adds a prop called changeState. But It does not appear to be working.
The code for Posts.js
import Post from "./post"
export default function posts() {
const posts = [<arrayOfPosts>];
const [favoritesChanged, setFavoritesChanged] = useState(1);
const changeState = () => {
setFavoritesChanged(Math.random());
}
useEffect(() => {
console.log("favorites changed");
}, [favoritesChanged])
return (
{posts.map((post) => {
<Post changeState={changeState} key={post.id} />
}
)
}
Then in the post.js file we have:
const Post = ({ changeState }) => {
console.log("change state: ", changeState);
return (
<View>
<TouchableOpacity onPress={changeState}>
<Text>Click here to test.</Text>
</TouchableOpacity>
<Text>{post.title}</Text>
</View>
)
}
export default Post
But the press action doesn't fire the changeState function and where it is being console.logged it says undefined. Why is this not working?

You are missing returning the Post component JSX in the .map callback:
return (
{posts.map((post) => {
return <Post changeState={changeState} key={post.id} post={post} />
})}
);
or using an implicit arrow function return:
return (
{posts.map((post) => (
<Post changeState={changeState} key={post.id} post={post} />
))}
);
Ensure you are destructuring all the props you need that are passed:
const Post = ({ changeState, post }) => {
return (
<View>
<TouchableOpacity onPress={changeState}>
<Text>Click here to test.</Text>
</TouchableOpacity>
<Text>{post.title}</Text>
</View>
)
};

Try using this as the return in the posts component:
return (
<>
{
posts.map(val => <Post changeState={changeState} key={val}/>)
}
</>
)
See this as a reference : Sandbox reference

Related

Rendering an <article> surrounding components in React

I'm trying to return a list of 'cards', each containing three components.
The parent, ItemCard, returns this:
<>
<article>
<ItemName data={items} />
<ItemMap data={items} />
<FavouriteButton data={items} />
</article>
</>
)
The child components each have a .map to render each item from the array:
const ItemName = (props) => {
return (
<>
{props.data.map((item) =>
<p key={item.title}>{item.title}</p>
)}
</>
)
}
I would like to surround the three components returned from ItemCard so that each instance is its own article. Currently I get one big article containing a list of ItemNames, then ItemMaps, then Buttons. I'd like 25 individual articles, each with ItemName, ItemMap and Button.
My only idea was to use a forEach to do this, but I can't get it working.
Any tips much appreciated!
Hi Hanna and welcome to stack overflow. You almost got the right answer with the data.map function. You just need to put the article inside the return of the map iterator.
const App = (props) => {
return (
<>
{props.data.map((item) => (
<ItemCard item={item} />
))}
</>
);
};
const ItemCard = ({item}) => {
return (
<article>
<ItemName item={item} />
<ItemMap item={item} />
<FavouriteButton item={item} />
</article>
);
};
You'll need to change the implementation of ItemName, ItemMap and FavouriteButton to account for the change in props structure.
Thanks so much for your help!
I've refactored with your suggestions in mind but can't get it working properly. I've now got the parent as ItemsListContainer, and this component fetches the data, stores it in state and returns the ItemCard(s).
const dataUrl = "https://s3-eu-west-1.amazonaws.com/olio-staging-images/developer/test-articles-v4.json"
const ItemsListContainer = () => {
const [items, setItems] = useState([])
const fetchItemData = async () => {
const response = await fetch(dataUrl)
const jsonData = await response.json()
setItems(jsonData)
console.log(items)
}
useEffect(() => {
fetchItemData()
}, [])
if(items.length > 0) {
return (
<>
{items.map((item) => (
<ItemCard item={item} />
))}
</>
)
} else {
return (
<div>Loading items...</div>
)
}
}
ItemCard returns the three components:
const ItemCard = ({item}) => {
return (
<>
<article>
<ItemName item={item} />
<ItemMap item={item} />
<FavouriteButton item={item} />
</article>
</>
)
}
ItemName returns each:
const ItemName = (props) => {
console.log(props.item.title)
return (
<>
{props.item.map((item) => (
<p key={item.title}>{item.title}</p>
))}
</>
)
}
The error I get in the console is 'Uncaught TypeError: props.item.map is not a function at ItemName (ItemName.js:6)'.
The console.log works and prints what I want displayed in the p tag.

how to delete a component in react?

I want to delete it.But it won't be deleted.
If you click the delete text in the modal, it should be deleted, but it doesn't work.What should I do to delete it?
There's an error saying that onRemove is not a function.Please help me.
I want to delete it.But it won't be deleted.
If you click the delete text in the modal, it should be deleted, but it doesn't work.What should I do to delete it?
There's an error saying that onRemove is not a function.Please help me.
export default function Modal({ onRemove, id }) {
return (
<OptionModalWrap>
<ModalWrapper>
<TitleWrap>Edit</TitleWrap>
<TitleWrap>Duplicate</TitleWrap>
<DeleteText onClick={() => onRemove(id)}>Delete</DeleteText>
</ModalWrapper>
</OptionModalWrap>
);
}
export default function GroupList({ title, onRemove }) {
const [showModal, setShowModal] = useState(false);
const optionModal = () => {
setShowModal(prev => !prev);
};
return (
<AdGroups>
<Header>
<Container>
<ActiveWrap>
<ActiveIcon src={Active} />
<SubTitle>Active</SubTitle>
</ActiveWrap>
<Alaram>
<Bell src={bell} />
<Text className="alarmCount">10</Text>
</Alaram>
</Container>
<EtcIconWrap>
<EtcIcon src={plus} onClick={optionModal} />
{showModal && (
<OptionModal showModal={showModal} onRemove={onRemove} />
)}
</EtcIconWrap>
</Header>
<GroupTitle>{title}</GroupTitle>
</AdGroups>
);
}
export default function GroupPage() {
const [Groupdata, setGroupData] = useState([]);
const onRemove = item => {
setGroupData(Groupdata.filter(item => item.id !== item));
};
useEffect(() => {
fetch('./data/AdsGroup/AdsGroupList.json')
.then(res => res.json())
.then(res => setGroupData(res));
}, []);
return (
<GroupPages>
{Groupdata.map(item => {
return (
<GroupList key={item.id} title={item.title} onRemove={onRemove} />
);
})}
</GroupPages>
);
}
You have not passed the id in GroupList and then also to the OptionModal component.
So here is the revised code:
Group Page Component:
Passing the id to GroupList Component
const onRemove = id => {
setGroupData(Groupdata.filter(item => item.id !== id)); // you were item.id !== item which was wrong
};
<GroupList key={item.id} title={item.title} id={item.id} onRemove={onRemove} /> // passing the id
Group List Component:
Added id in the props and passed that to Modal Component. Also calling optionModal function to close the Modal after it deleted
export default function GroupList({ id, title, onRemove }) {
const [showModal, setShowModal] = useState(false);
const optionModal = () => {
setShowModal(prev => !prev);
};
return (
<AdGroups>
<Header>
<Container>
<ActiveWrap>
<ActiveIcon src={Active} />
<SubTitle>Active</SubTitle>
</ActiveWrap>
<Alaram>
<Bell src={bell} />
<Text className="alarmCount">10</Text>
</Alaram>
</Container>
<EtcIconWrap>
<EtcIcon src={plus} onClick={optionModal} />
{showModal && (
<OptionModal id={id} showModal={showModal} onRemove={onRemove;optionModal} />
)}
</EtcIconWrap>
</Header>
<GroupTitle>{title}</GroupTitle>
</AdGroups>
);
}
Modal Component: No change in this component
export default function Modal({ onRemove, id }) {
return (
<OptionModalWrap>
<ModalWrapper>
<TitleWrap>Edit</TitleWrap>
<TitleWrap>Duplicate</TitleWrap>
<DeleteText onClick={() => onRemove(id)}>Delete</DeleteText>
</ModalWrapper>
</OptionModalWrap>
);
}
Didn't your IDE complaint about this piece of code? both of the onRemove & filter functions' props are called item, it shouldn't be.
const onRemove = itemId => {
setGroupData(Groupdata.filter(item => item.id !== itemId));
};

Send ref via props in functional component

In my parent component I call hook useRef: const flatListRef = useRef(null); and then I want to use this flatListRef in child component. I tried to do like in documentation but without success. When I call my function toTop I get: null is not an object (evaluating 'flatListRef.current.scrollToOffset')
This is my parent component:
const BeautifulPlacesCards = ({navigation}: HomeNavigationProps<"BeautifulPlacesCards">) => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1)
}
const buttonPressed = () => {
toTop()
}
return(
<Carousel filteredData={filteredData} flatListRef={flatListRef}/>
)
}
This is my child component:
const Carousel = forwardRef((filteredData, flatListRef) => {
return (
<AnimatedFlatList
ref={flatListRef}
/>
)
}
Here is a working example: https://snack.expo.dev/#zvona/forwardref-example
Key takes:
you need to use prop ref when passing it down, not flatListRef
you need to destructure filteredData from props
Here is the relevant code:
const Child = forwardRef(({ filteredData }, ref) => {
return (
<FlatList
ref={ref}
style={styles.flatList}
data={filteredData}
renderItem={({ item }) => (
<Text style={styles.item} key={`foo-${item}`}>
{item}
</Text>
)}
/>
);
});
const App = () => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1);
};
return (
<View style={styles.container}>
<Button title={'Scroll back'} onPress={toTop} />
<Child filteredData={[1,2,3,4,5,6]} ref={flatListRef} />
</View>
);
};

React Native - state hook updates and it's re-rendering the component, but nothing shows

so here's the code:
export default () => {
const [albums, setAlbums] = useState([]);
useEffect(() => {
MediaLibrary.getAlbumsAsync().then((tmpAlbums) => {
setAlbums(tmpAlbums);
});
}, []);
return (
<View>
{albums && (
<FlatList
data={albums}
renderItem={({ item }) => {
<Text>{item.title}</Text>;
}}
/>
)}
</View>
);
};
I'm sure that state updates because I logged it and it was updated, I already have the permissions and I've just removed it for simplicity. I've tried everything and yet, nothing shows on the component/screen.
You are not returning the Text.
Either do
{albums && (
<FlatList
data={albums}
renderItem={({ item }) => {
return (<Text>{item.title}</Text>)
}}
/>
)}
Or
{albums && (
<FlatList
data={albums}
renderItem={({ item }) => (
<Text>{item.title}</Text>)
)}
/>
)}

Sending data from Child to Parent React

I have subdivided my components and I want to change state of text using deleteName function from child component. However I have used onPress={this.props.delete(i)} in my child component which is not working. The error that occurs for me is:
undefined variable "I"
Here is my code:
App.js
export default class App extends Component {
state = {
placeName: '',
text: [],
}
changeName = (value) => {
this.setState({
placeName: value
})
}
deleteName = (index) => {
this.setState(prevState => {
return {
text: prevState.text.filter((place, i) => {
return i!== index
})
}
}
}
addText = () => {
if (this.state.placeName.trim === "") {
return;
} else {
this.setState(prevState => {
return {
text: prevState.text.concat(prevState.placeName)
};
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.inputContainer}>
<Input changeName={this.changeName}
value={this.state.placeName} />
<Button title="Send" style={styles.inputButton}
onPress={this.addText} />
</View>
<ListItems text={this.state.text} delete={this.deleteName}/>
{/* <View style={styles.listContainer}>{Display}</View> */}
</View>
);
}
}
and child component ListItems.js
const ListItems = (props) => (
<View style={styles.listitems}>
<Text>{this.props.text.map((placeOutput, i) => {
return (
<TouchableWithoutFeedback
key={i}
onPress={this.props.delete(i)}>
onPress={this.props.delete}
<ListItems placeName={placeOutput}/>
</TouchableWithoutFeedback>
)
})}
</Text>
</View>
);
You need to bind the index value at the point of passing the props to the child.
delete = index => ev => {
// Delete logic here
}
And in the render function, you can pass it as
items.map((item, index) => {
<ChildComponent key={index} delete={this.delete(index)} />
})
In your child component, you can use this prop as
<button onClick={this.props.delete}>Click me</button>
I have created a Sandbox link for your reference
Instead of onPress={this.props.delete(i)}, use onPress={() => this.props.delete(i)}
In order to have the cleaner code, you can use a renderContent and map with }, this);like below. Also you need to use: ()=>this.props.delete(i) instead of this.props.delete(i) for your onPress.
renderContent=(that)=>{
return props.text.map((placeOutput ,i) => {
return (
<TouchableWithoutFeedback key={i} onPress={()=>this.props.delete(i)}>
onPress={this.props.delete}
</TouchableWithoutFeedback>
);
}, this);
}
}
Then inside your render in JSX use the following code to call it:
{this.renderContent(this)}
Done! I hope I could help :)

Resources