I'm trying to create list with an altered style based on selection:
I have a dynamic state -
constructor(props) {
super(props);
this.state = {
inventoryArray: InvData.map(obj => ({...obj, clicked: false})),
}
}
I want to use the clicked state of each object to determine styling - but am not quite sure on th syntax to use - i've tried:
returnInventoryObjects = () => {
return (
this.state.inventoryArray.map((data, i) => {
return (
if({data.clicked}) ? {
<View key={i} style={[InventoryStyles.InventoryItem, InventoryStyles.InventoryItemSel ]}>
}
else{
<View key={i} style={[InventoryStyles.InventoryItem]}>
}
which doesn't work -
I've also tried an inline dynamic style ie -
returnInventoryObjects = () => {
return (
this.state.inventoryArray.map((data, i) => {
return (
<View key={i} style={[InventoryStyles.InventoryItem, {data.clicked} && InventoryStyles.InventoryItemSel ]}>
Can anyone please advise how to achieve this please?
Heres the error shown when implementing option 2 -
Styles used are:
InventoryItem:{
backgroundColor:'rgba(255,255,255,0.8)',
paddingVertical:8,
paddingHorizontal:8,
flexDirection:'row',
marginBottom:15,
},
InventoryItemSel:{
backgroundColor:'rgba(255,255,255,0.2)',
},
I think you tried to make conditional style like so:
<View
key={i}
style={
data.clicked
? [InventoryStyles.InventoryItem]
: [InventoryStyles.InventoryItem, InventoryStyles.InventoryItemSel]
}
/>
I would do:
// More maintanable
const DEFAULT = [InventoryStyles.InventoryItem];
<View
style={
data.clicked ? DEFAULT : [...DEFAULT, InventoryStyles.InventoryItemSel]
}
/>
Notice that the statement {data.clicked} is parsed as an expression within scope, and not as an object or something else that you meant.
So using it within an array or in if expression, is an syntax error.
{ // Scope
data.clicked // Expression
}
Please try this:
this.state.inventoryArray.map((data, i) => {
if(data.clicked) {
return (
<View key={i} style={[InventoryStyles.InventoryItem, InventoryStyles.InventoryItemSel ]} />
)
}
else {
return (
<View key={i} style={[InventoryStyles.InventoryItem]} />
)
}
}
React components that don't have children need to be self-closing (/ at the end). Notice the difference between
<View key={i} style={[InventoryStyles.InventoryItem, InventoryStyles.InventoryItemSel ]}>
and
<View key={i} style={[InventoryStyles.InventoryItem, InventoryStyles.InventoryItemSel ]} />
Be sure to correct this in all your View components
The rest looks fine!
Related
I'm really confused on how to add (and delete) View and other such components during runtime,
for example in vanilla JavaScript you can use document.querySelector('query-here').appendChild(element);
but how do I achieve the same thing using react native? for example:
<Pressable onPress={()=>{addElement(element)}}>
<View>
//add elements here
</View>
I know how to achieve it directly like this:
<View>
{
[...Array(23)].map((el, index) => {
return(
<View key={index}>
<Text>added new element</Text>
</View>
)});
}
</View>
could someone please point me in the right direction?
#cakelover here how you can add item and remove items based on component's state.
import { Button } from 'react-native';
const [loader, setLoader] = React.useState(false); //donot show loader at initial
const showLoader = isShowLoader => { // based on this function you can add or remove single loader from UI
setLoader(isShowLoader);
}
return (
<View>
{loader && <LoaderComponent/>}
<Button
onPress={() => setLoader(!loader)}
title="Toggle Loader Component"
color="#841584"
/>
</View>
)
If you want to add or remove multiple same components like list you should use arrays of items for that.
I'm not sure but maybe you could try something like this
export default function App() {
const [num, setNum] = useState(() => 0);
const [renderTasks, setRenderTasks] = useState(()=>taskcreate(0));
function taskcreate()
{
let i=num;
setNum(i+1);
return(
<View>
{
[...Array(i)].map((el, index) => {
return (
<View key={index}>
<Text>hello there</Text>
</View>
)
})
}
</View>
)
}
return (
<View style={styles.container}>
<Pressable style={{ height: 50, width: 50, backgroundColor: 'orange' }} onPress={() => { setRenderTasks(taskcreate()) }}></Pressable>
{ renderTasks }
</View>
);
}
Before you mark this question as a duplicate of a closure issue or a event binding issue, know that I have tried all of those and it does not work.
A working demo of the issue. Open the link. Run the project on android and check logs. If you click on any option in the first question, it will log the lastIndex (Here it is 4. Since there are a total of 5 questions) when it should log the firstIndex which is 0.
So I am using javascript map to loop over my questions array and return questions and their respective options. Inside I have another map to loop over options.
The checkbox is a react-native-elements component.
renderCard = (item, index) => {
return (
<Card style={styles.testCard}>
<View key={item.u_question_id}>
<Text style={styles.question}>{index + 1}. {item.question}</Text>
{item.options.map(({ option, checked }, i) => {
return (
<View key={i}>
<CheckBox
containerStyle={{ backgroundColor: "transparent", borderWidth: 0 }}
title={option}
checkedIcon='dot-circle-o'
uncheckedIcon='circle-o'
checked={checked}
onPress={() => this.onSelectOption(index, i)}
/>
</View>
)
})}
</View>
</Card>
)
}
What I am expecting is that onPress of a checkbox should send the questionIndex and the optionIndex into onSelectOption to change the state and view but onPress always sends the last index of the questions array so the options of the last question are getting changed and not the intended one.
My onSelectOption method. Here questionIndex is coming 4 if I have 5 questions even though I am pressing on the first question's options.
onSelectOption = (questionIndex, optionIndex) => {
const selectedLanguageQuestionsCopy = this.state.selectedLanguageQuestions;
selectedLanguageQuestionsCopy[questionIndex].options.forEach(option => {
option.checked = false;
});
selectedLanguageQuestionsCopy[questionIndex].options[optionIndex].checked = true;
this.setState({ assessmentData: selectedLanguageQuestionsCopy });
}
I have tried using:
onPress={() => this.onSelectOption(index, i)}
onPress={this.onSelectOption.bind(this, index, i)} and changing onSelectOption to a normal method instead of an array function.
But it does not work. I am always getting the last index of the questions array.
The place where I am calling the renderCard method. selectedLanguageQuestions is an array of objects.
<Swipe
data={selectedLanguageQuestions}
activeQuestion={activeQuestion}
renderCard={(item, i) => this.renderCard(item, i)}
onSwipeLeft={(ques, i) => this.setState({ activeQuestion: i })}
onSwipeRight={(ques, i) => this.setState({ activeQuestion: i })}
/>
Render method of Swipe:
render() {
return (
<View>
{this.renderCards()}
</View>
);
}
renderCards() {
return this.props.data.map((item, i) => {
if (i === this.state.index) {
return (
<Animated.View
key={i}
style={[this.getCardStyle(), styles.cardStyle]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
);
}
return (
<Animated.View
key={i}
style={[styles.cardStyle, { opacity: 0 }]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
)
});
}
It seems like on Swipe.js line 137
return (
<Animated.View
key={i}
style={[styles.cardStyle, { opacity: 0 }]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
)
You are keeping the answers above each others, so what's happening exactly is that you are clicking on the answers that have 0 opacity, but because you don't see them, you think the visible answers are those getting the event. So what I suggest is that you disable the events on the answers that have 0 opacity, like so:
return (
<Animated.View
key={i}
pointerEvents="none"
style={[styles.cardStyle, { opacity: 0 }]}
{...this.panResponder.panHandlers}
>
{this.props.renderCard(item, i)}
</Animated.View>
)
Just added pointerEvents="none" attribute; to see how the questions were interrupting your "press", set the opacity to something above 0.5 and you will see the problem.
I hope this solves your issue.
In this example, I want to show and hide feature for the content of accordion.
This is my source code.
state = {
check: true,
};
renderContent = (order, _, isActive) => {
//Accordion Content
return (
<View style={styles.child}>
<View>
<Text>Hello World</Text>
</View>
<View>
<Text>Goodbye</Text>
</View>
}
</View>
)
}
render() {
return (
<View style={styles.scrollMain}>
<CheckBox
value = { this.state.check}
onValueChange={(newValue) => this.setState({check:newValue})}/> // this is my checkbox
<Accordion
.....
renderContent={this.renderContent}
}/>
}
</View>
);
What I want to do is if I click to checkbox, it should hide this content below
`<View>
<Text>Goodbye</Text>
</View>`
When I uncheck checkbox it should go back. Give me please idea how I can do it?
Hi you almost have it done, you just have to think the this.state.check condition is the one that will handle the content so you have several options to achieve this:
You can create another component to handle this logic.
Instead use a JSX block to handle this logic.
Or you can use css with a class or something similar to just hide the content but the elements will always exist.
I think that this decision invovles your use case but a straightforward way to do it is encapsulating the logic in a JSX block
renderContent = (order, _, isActive) => {
//Accordion Content
return (
<View style={styles.child}>
<View>
<Text>Hello World</Text>
</View>
{
!this.state.check?
<View>
<Text>Goodbye</Text>
</View>
: null
}
}
</View>
)
}
Try this condition, if you want other behaviors just tell me
renderContent = (order, _, isActive) => {
//Accordion Content
return (
<View style={styles.child}>
<View>
<Text>Hello World</Text>
</View>
{!this.state.check &&
<View>
<Text>Goodbye</Text>
</View>}
}
</View>
)
}
How do I get rid of this warning? I know I need to get rid of setState functions in the render method, but I need them, so where should I put them?
export default class List<T> extends React.PureComponent<ListProps<T>> {
state = { wrapped: false, iconName: "arrow-down" };
render(): React.Node {
const { rows, renderRow, title, onPress } = this.props;
if (this.state.wrapped === true) {
list = undefined;
this.setState({ iconName: "arrow-up" });
} else {
list = rows.map((row, index) => (
<View key={index} style={index !== rows.length - 1 ? styles.separator : {}}>
{renderRow(row, index)}
</View>
));
this.setState({ iconName: "arrow-down" });
}
return (
<TouchableWithoutFeedback>
<View style={styles.container}>
<View style={[styles.separator, styles.relative]}>
<Text style={styles.title}>{title}</Text>
<IconButton
style={styles.icon}
onPress={() => this.setState({ wrapped: !this.state.wrapped })}
name={this.state.iconName}
color="black"
/>
</View>
{list}
</View>
</TouchableWithoutFeedback>
);
}}
No, you don't need to get rid of setState calls in your render method in general. You just need to put them so that they are not called in each render call (by binding them to user events like clicks for example) and thereby trigger another re-render, that again calls setState and again re-renders and so on.
So in your particular case, you are firing setState right in the beginning in the if() { ... } else { ... } statements. No matter what this.state.wrapped is, you end up at setState.
Here is a possible solution for how you might want to change your code specifically to make it what I assume you want it to make:
export default class List<T> extends React.PureComponent<ListProps<T>> {
state = { wrapped: false };
render(): React.Node {
const { rows, renderRow, title, onPress } = this.props;
const { wrapped } = this.state;
return (
<TouchableWithoutFeedback>
<View style={styles.container}>
<View style={[styles.separator, styles.relative]}>
<Text style={styles.title}>{title}</Text>
<IconButton
style={styles.icon}
onPress={() => this.setState({ wrapped: !wrapped })}
name={wrapped ? "arrow-up" : "arrow-down"}
color="black"
/>
</View>
{!wrapped && (
<View key={index} style={index !== rows.length - 1 ? styles.separator : {}}>
{renderRow(row, index)}
</View>
)}
</View>
</TouchableWithoutFeedback>
);
}}
Because the value of your icon is directly correlated to wrapped, you don't need to specifically set the icon in the state. Rather infer it from wrapped.
I have created amenitiesArray which has elements like club_house, swimming_pool, etc. I am mapping over this amenitiesArray using .map() but when I pass it as a prop I am not able to display it on screen why so?
console.log(amenitiesArray) is:
0:"Gymnasium"
1:"swimming_pool"
2:"club_house"
3:"childrens_play_area"
4:"multipurpose_room"
5:"security"
6:"jogging_track"
7:"power_backup"
8:"landscaped_gardens"
9:"car_parking"
Code:
const DisplayAmenities = ({ name }) => (
<Text style={{ color: 'black', fontSize: 16 }}>{name}</Text>
);
export default class Project extends Component {
render() {
let amenitiesArray = [];
for (var key in amenities[0]) {
if (amenities[0][key] === 'Yes') {
amenitiesArray.push(key);
}
}
console.log(amenitiesArray);
return (
<ScrollView>
<View style={{ marginTop: 20 }}>
{amenitiesArray.map(val => {
<DisplayAmenities name={val} />;
})}
</View>
</ScrollView>
);
}
}
Above code does not display anything on screen? Why so?
Because you forgot to return the component. Try to fix like this:
return (
<ScrollView>
<View style={{ marginTop: 20 }}>
{amenitiesArray.map((val,index) => {
return <DisplayAmenities key={index} name={val} />; //fixed
})}
</View>
</ScrollView>
);
For short, you could omit the curly brackets, like so
return (
<ScrollView>
<View style={{ marginTop: 20 }}>
{amenitiesArray.map((val,index) => <DisplayAmenities key={index} name={val} /> )}
</View>
</ScrollView>
);
{amenitiesArray.map(val => {
<DisplayAmenities name={val} />;
})}
lacks explicit return, map callback function maps an array to an array of undefined.
For implicit arrow return, it should be:
{amenitiesArray.map(val =>
<DisplayAmenities name={val} />;
)}
The use of array-callback-return ESLint rule allows to avoid this sort of mistakes.