Child component not re rendering after Parent state changes - reactjs

I have a RadioGroup component that contains multiple RadioButton components. Here's the code for the RadioGroup component:
const RadioGroup = ({radioGroupData}) => {
const [radioGroupRefreshData, setRadioGroupRefreshData] = useState(radioGroupData);
const handleClick = (index) => {
setRadioGroupRefreshData(radioGroupRefreshData.map((obj, i) => {
if(i !== index) {
return {text: obj.text, isSelected: false};
}
return {text: obj.text, isSelected: true};
}));
};
return (
<View style={styles.container}>
{
radioGroupRefreshData.map((obj, i) => {
return <RadioButton index={i}
text={obj.text}
isSelected={obj.isSelected}
onClick={handleClick} />
})
}
</View>
);
}
The RadioGroup component has a state variable (an array) called radioGroupRefreshData. when each RadioButton is defined inside the RadioGroup, the handleClick function is passed as a prop in order to be called when a RadioButton is clicked. Here is the code for the RadioButton component:
const RadioButton = (props) => {
const [isSelected, setIsSelected] = useState(props.isSelected);
const initialRenderDone = useRef(false);
useEffect(() => {
if(!initialRenderDone.current) {
initialRenderDone.current = true;
}
else {
props.onClick(props.index);
}
}, [isSelected]);
const handlePress = () => {
if(!isSelected) {
setIsSelected(true);
}
}
return (
<TouchableOpacity style={styles.outsideContainer} onPress={handlePress}>
<View style={styles.radioButtonContainer}>
{ (isSelected) && <RadioButtonInnerIcon width={15} height={15} fill="#04004C" /> }
</View>
<Text style={styles.radioButtonText}>{props.text}</Text>
</TouchableOpacity>
);
}
From what I know, each RadioButton component should re render when the Parent's variable radioGroupRefreshData changes, but the RadioButton component's are not re rendering.
Thank you in advance for any help that you can give me!

Since you have a state in RadioButton you need to update it when the props change. So in RadioButton add useEffect like this:
useEffect(() => {
setIsSelected(props.isSelected);
},[props.isSelected]);
Also you don't have to mix controlled and uncontrolled behaviour of the component: do not set RadioButton state inside RadioButton since it comes from the RadioGroup

Related

How to set parent state from FlatList component?

I have a PaymentMethodsScreen screen. On this screen there is a FlatList with PaymentCardItem components inside. And there is a checkbox inside the PaymentCardItem. When this checkbox checked I would like to update selectedCardToken state of PaymentMethodsScreen. But unfortunately I couldn't figure out how to do it. I tried to pass props but I was doing it wrong. Here is my code (without passing props).
How can I achieve that? Thank you very much for your helps.
const PaymentCardItem = ({ family, association, bin_number, token, isSelected }) => (
<View>
<RadioCheckbox
selected={ isSelected }
onPress={ () => this.setSelectedCardToken(token) // Something wrong here }
/>
<Text>{family}, {association}</Text>
<Text>{bin_number}**********</Text>
</View>
);
const PaymentMethodsScreen = ({navigation}) => {
const {state} = useContext(AuthContext);
const [cardList, setCardList] = useState(null) // This stores card list data from API request
const [selectedCardToken, setSelectedCardToken] = useState('test token')
const renderItem = ({ item }) => (
<PaymentCardItem
bin_number={item.bin_number}
family={item.family}
association={item.association}
token={ item.token }
isSelected={ (selectedCardToken == item.token) }
/>
);
return (
<SafeAreaView>
<View>
<FlatList
data={cardList}
renderItem={renderItem}
keyExtractor={item => item.alias}
/>
</View>
</SafeAreaView>
);
};
add onPress prop to PaymentCardItem:
// PaymentMethodsScreen
<PaymentCardItem
onPress={() => setSelectedCardToken(item.token)}
>
I don't know how the PaymentCardItem component is structured, but generally you should add onPress prop on the TouchableOpacity in the component or whatever is your onPress handler:
// PaymentCardItem component
<TouchableOpacity
onPress={() => props.onPress()}
>
You can pass down the handler function which gets called on checkbox being checked or unchecked to your PaymentCardItem component.
You can also pass setSelectedCardToken directly, but in case you have some extra logic before you update state, it's better to have a handler for more readability.
So, the code will be like below.
const PaymentMethodsScreen = ({ navigation }) => {
const { state } = useContext(AuthContext);
const [cardList, setCardList] = useState(null) // This stores card list data from API request
const [selectedCardToken, setSelectedCardToken] = useState('test token')
const handleCardTokenSelection = (isTokenSelected) => {
if(isTokenSelected) {
setSelectedCardToken(); // whatever logic you have
} else {
setSelectedCardToken(); // whatever logic you have
}
}
const renderItem = ({ item }) => (
<PaymentCardItem
bin_number={item.bin_number}
family={item.family}
association={item.association}
token={ item.token }
isSelected={ (selectedCardToken == item.token) }
handleCardTokenSelection={handleCardTokenSelection}
/>
);
return (
<SafeAreaView>
<View>
<FlatList
data={cardList}
renderItem={renderItem}
keyExtractor={item => item.alias}
/>
</View>
</SafeAreaView>
);
};
const PaymentCardItem = ({ family, association, bin_number, token, isSelected, handleCardTokenSelection }) => (
<View>
<RadioCheckbox
selected={ isSelected }
onPress={handleCardTokenSelection}
/>
<Text>{family}, {association}</Text>
<Text>{bin_number}**********</Text>
</View>
);
You need to set the state for PaymentCardItem not for the whole Flatlist, to show the item is selected.
I think you update the PaymentCardItem component to something like the below code(You can update the logic as per requirement)
class PaymentCardItem extends React.Component {
constructor(props) {
super(props);
this.state = {selectedCardToken: "", isSelected: false};
}
setSelectedCardToken=(token)=>{
if(selectedCardToken == token){
this.setState({
selectedCardToken: token,
isSelected: true
})
}
}
render() {
const { family, association, bin_number, token }=this.props;
const { isSelected } = this.state;
return (
<View>
<RadioCheckbox
selected={ isSelected }
onPress={ () => this.setSelectedCardToken(token)
/>
<Text>{family}, {association}</Text>
<Text>{bin_number}**********</Text>
</View>
);
}
}

Auto focus Input within React Native Viewpager

I'm using a React Native Viewpager to take in user entry, and move to the next page on button press. Important to note that moving to the next page happens on button press, and not by normal scrolling, which is disabled.
The best way I could think to handle this was to have a state on the ViewPager, which would propagate into the child Entries.
ViewPager.tsx:
export default function ViewPager({ route, navigation }) {
const ref: React.RefObject<ViewPager> = React.createRef();
const [currentPage, setCurrentPage] = useState(0);
let setEntryPage = (page: number) => {
ref.current?.setPage(page);
setCurrentPage(page);
}
return (
<View style={{flex: 1}}>
<ViewPager
style={styles.viewPager}
initialPage={0}
ref={ref}
scrollEnabled={false}
>
{
GlobalStuff.map((entry, index) => {
return (
<Entry
key={index}
index={index}
pagerFocusIndex={currentPage}
pagerLength={quizDeck?.litems.length!}
setEntryPage={setEntryPage}
/>
)
})
}
</ViewPager>
</View>
);
};
Entry.tsx:
export function Entry(props: EntryProps) {
const inputRef: React.RefObject<Input> = React.createRef();
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
return (
<View>
<Input
// ...
ref={inputRef}
/>
<IconButton
icon="arrow-right-thick"
color={colorTheme.green}
onPress={() => {
props.index !== props.pagerLength - 1 ?
props.setEntryPage(props.index + 1) :
props.navigation!.reset({ index: 0, routes: [{ name: recapScreenName as any }] });
}}
/>
// ...
Unfortunately, inputRef appears to be null, and there is probably a better way of achieving what I'm trying to achieve anyway.
Anything in your render loop will be called every time the component renders.
// This is called on every render
const inputRef: React.RefObject<Input> = React.createRef();
// So is this, it's always null
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
Put side effects in effects.
// Untested
const inputRef = useRef();
useEffect(() => {
if (props.pagerFocusIndex === props.index) {
inputRef.current?.focus();
}
}, [inputRef.current, props.pagerFocusIndex, props.index]);

How to pass data in react js inside a file

I have the next component in ReactJs:
function App() {
function handleClick(e) {
console.log('click ', e);
localStorage.setItem('my-item', e.key);
console.log(localStorage.getItem('my-item'))
}
return (
<div>
<Menu
selectedKeys={e.key}
style={{ width: 500px}}
onClick={handleClick}
>
...another code
}
There i used AntDesign. The function handleClick write in my localstorage the key of an item. It works, but i want to pass in selectedKeys the key that i passed in the localstorage, so i want to do this selectedKeys={e.key}, but i can't do this. How to pass the data e.key from the function to<Menu> tag?
What about using a state :
With the React hook useState :
import React, { useState } from React;
then
const [selectedKeys, setSelectedKeys] = useState([]);
then
function handleClick(key) {
localStorage.setItem('my-item', key);
let newKeys = selectedKeys.slice(0); // you may need to clone the array
newKeys.push(key);
setSelectedKeys(newKeys); // or push it into an array if there is many.
}
your Menu component would then look like :
<Menu
mode="inline"
selectedKeys={selectedKeys}
openKeys={openKeys}
onOpenChange={onOpenChange}
style={{ width: 500px}}
onClick={() => handleClick(e.key)}>
All together :
import React, { useState } from React;
const App = () => {
const [selectedKeys, setSelectedKeys] = useState([]);
const handleClick = (key) => {
localStorage.setItem('my-item', key);
let newKeys = selectedKeys.slice(0); // you may need to clone the array
newKeys.push(key);
setSelectedKeys(newKeys); // or push it into an array if there is many.
}
return {
<Menu
mode="inline"
selectedKeys={selectedKeys}
openKeys={openKeys}
onOpenChange={onOpenChange}
style={{ width: 500px}}
onClick={() => handleClick(e.key)}>
}
}
Make it a class component and in the state make an attribute of selected key and in the handleClick do set the state and assign the selectedKey value from the state in the Menu tag. Thats the proper way of doing it.
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
selectedKey: null
};
}
handleClick = (e) => {
console.log('click ', e);
this.setState({
selectedKey: e.key
});
localStorage.setItem('my-item', e.key);
console.log(localStorage.getItem('my-item'));
}
render () {
return (
<div>
<Menu
selectedKeys={this.state.selectedKey}
style={{ width: 500px}}
onClick={handleClick}
>
...another code
}
}
Use an arrow function:
onClick={() => handleClick(e.key)}
In your code:
handleClick = key => {
console.log("the key is:", key);
localStorage.setItem('my-item', key);
console.log(localStorage.getItem('my-item'))
}
<Menu
selectedKeys={e.key}
style={{ width: 500px}}
onClick={() => handleClick(e.key)}>

Cant close modal triggered from parent component

I am triggering a react-native modal from a parent component, the modal shows up, when I try close the modal with a button in the child component / model itself, nothing happens.
Any help would be appreciated.
This is in child component
state = {
display: false,
};
setModalVisible(visible) {
this.setState({display: visible});
}
onPress={() => {
this.setModalVisible(!this.state.display);
}}
This is in parent component
<DisplayModal display={this.state.display} />
triggerModal() {
this.setState(prevState => {
return {
display: true
};
});
}
this.triggerModal()
You should negate the modal value to open and close it
triggerModal() {
this.setState(prevState => {
return {
display: !prevState.display
};
});
}
And instead of passing the state to setModalVisible, you can just use the callback setState.
setModalVisible() {
this.setState(prevState => ({display: !prevState.display}));
}
onPress={() => {
this.setModalVisible();
}}
Edit:
You added your code and here is something that might be the problem
ADD_DETAILS(index) {
if (index === 0) {
console.log("clicked 0");
this.triggerModal();
console.log(this.state.display);
}
}
The reason why it doesn't open and closes can be because it doesn't pass that if condition.
Edit 2:
Now I see your problem, what you need to do is call triggerModal in the children.
So here is what you need to do
Pass triggerModal as props to the children
Call it when you want to close the modal.
So here is the change in the parent.
<DisplayModal display={this.state.display} triggerModal={this.triggerModal}/>
And in the child
onPress={() => {
this.props.triggerModal();
}}
try this:
in parent =>
state = {
display: false,
};
setModalVisible() {
this.setState({display: !this.state.display});
}
<DisplayModal handlePress = this.setModalVisible.bind(this) display={this.state.display} />
then in child:
onPress={() => {
this.props.handelPress();
}}
This is how I approached it:
function App() {
const [showModal, setShowModal] = useState(true);
const closeModal = () => setShowModal(false);
const activateModal = () => setShowModal(true);
return showModal ? (
<DisplayModal closeModal={closeModal} display={showModal} />
) : (
<button onClick={activateModal}>Display modal!</button>
);
}
const DisplayModal = props =>
props.display ? (
<>
<div>Display!!</div>
<button onClick={props.closeModal}>Hide!</button>
</>
) : null;
Pay no attention to hooks, just notice the pattern I have used, passing the closeModal function as a prop and calling it whenever it is the case, but from the child component.
at first
inital
state ={
display: false}
create function handalepress
handalePress=()=>{
this.setState({
display:!this.state.display
}
<DisplayModal onClick= {this.handalePress}display={this.state.display} />

React native section list not re-rendering when item changes

I have a sectionList in my react native project. it does not re-render if item changes.
My code:
test.js
class Test extends React.Component {
started = false;
causeData=[];
showLess=false;
items = [];
_start = () => {
const { ws } = this.props;
this.showLess = false;
if (ws.causes.length) {
this.causeData = {
title: Language.causes,
key: "cause",
data: []
};
ws.causes.forEach(cause => {
let causeDetails = {
key: "cause_" + cause.id,
name: "",
value: cause.name,
sortIndex: cause.sortIndex,
progress: cause.progress
};
this.causeData.data.push(causeDetails);
if (this.causeData.data.length > 4) {
this.causeData.data = this.causeData.data.slice(0, 4);
}
});
this.items.push(this.causeData);
console.log("causeData", this.causeData);
}
}
}
_renderItem = ({ item }) => {
return (
<View>
<Text key={item.key} style={styles.text}>{`${item.name} ${
item.value
}`}</Text>
</View>
);
};
_renderSectionHeader = ({ section }) => {
const { ws } = this.props;
const showMore = ws.causes.length > 0 && !this.showLess;
return (
<View style={styles.sectionHeader}>
<Text key={section.key} style={styles.header}>
{section.title}
</Text>
{showMore && (
<Button
onPress={this._afterCauseAnswered}
title={Language.showMore}
data={this.items}
accessibilityLabel={Language.causeDoneAccessibility}
/>
)}
</View>
);
};
_keyExtractor = (item, index) => item.key;
_afterCauseAnswered = () => {
const { stepDone, ws } = this.props;
this.causeData.data = { ...ws.causes };
stepDone("showMoreAnswered");
this.showLess = true;
};
render = () => {
if (!this.started) {
this.started = true;
this._start();
}
return (
<View style={styles.container}>
<SectionList
sections={this.items}
extraData={this.items}
renderItem={this._renderItem}
renderSectionHeader={this._renderSectionHeader}
keyExtractor={this._keyExtractor}
/>
</View>
);
};
}
in my section list section header contain a button called showMore. At initial rendering it will only show 5 items, while clicking showMore it should display all List. This is my functionality. but while clicking showMore button it will not show entire list only shows 5 items that means the sectionList does not getting re-render. How to resolve this? i am new to react native. Any idea what am I missing ? Any help would be greatly appreciated!
Keep items and showLess in a state and after pressing Button call setState with the new values. It will rerender the SectionList. Also, if you want to display multiple items with a displayed list, you need to move showLess to the item element so each item knows how to display it.
You just need to rerender your screen using state and it's done
this.setState({})
Your SectionList should always read from state ... as it should be your single source of truth
and here's how:
class YourComponent extends React.Component {
state = {
items: [],
};
// This will be called after your action is executed,
// and your component is about to receive a new set of causes...
componentWillReceiveProps(nextProps) {
const {
ws: { causes: nextCauses },
} = nextProps;
if (newCauses) {
// let items = ...
// update yout items here
this.setState({ items });
}
}
}

Resources