How to get a component from an array and change its props in react native? - react-native

I've created some components in a for loop and push them to an array. Can i get any component from this array to change its props -like title ?
fields = [];
for (let i = 0; i < assets.fieldNames[assets.systemLang].length; i++) {
fields.push(
<InfoField
handlePress={() => this.fieldPressed(i)}
key={i}
title={assets.fieldNames[assets.systemLang][i]}
value="" />
);
};
.....
<View style={styles.infoFields}>
{
fields
}
</View>
and i have a function that i need something like this
changeComponentTitle = () => {
fields[indexForComponent].props.title = "new Title"
}

You'll need to make sure that you trigger a re-render in some way after you make the update (perhaps by putting fields in a state variable and calling setState as mentioned in the comments) but you can use cloneElement to essentially update a single prop of an existing element: https://reactjs.org/docs/react-api.html#cloneelement
changeComponentTitle = () => {
fields[indexForComponent] = React.cloneElement(fields[indexForComponent], {title: "new Title"});
}

Related

Passing state Array to child components in React

I'm trying to pass two array variables to a child component in react. I know it's something simple and I'm being stupid but I jest need a hand.
I make two API calls in two components that are in the same file. They should be adding the iterable data into the variable using the set methods. By the time the call to the component comes, the variables are undefined.
What am I doing wrong?
const ShowResults = props => {
const { pk } = props;
const [attributionsData, setAttributionsData] = useState([])
const [interactionsData, setInteractionsData] = useState([])
function ListAttributions({ pk }) {
const { loading, error, data } = useQuery(GET_ATTRIBUTIONS, {
variables: { pk },
});
if ( loading ) return <h2>LOADING... </h2>;
if ( error ) return `Error! ${error}`;
if ( data !== undefined){
setAttributionsData(data.listAttributions.items);
return <div>
{attributionsData.map(({ sk, nominal, organisation, attribution, file_name, datetime_added, exhibit }, index) => (
<AttributionsCard
key={index}
Sk={sk}
Nominal={nominal}
Organisation={organisation}
Attribution={attribution}
FileName={file_name}
FoundInsidePhone={file_name.match(/[0-9]+/g)}
DateTimeAdded={datetime_added}
Exhibit={exhibit}
Pk ={pk}
/>
))}
</div>
}
}
function ListInteractions({ pk }) { //Aug 2022 - This is a working prototype of the query behavious i'm looking for.
const { loading, error, data } = useQuery(GET_INTERACTIONS, {
variables: { pk },
});
if ( loading ) return <h2>LOADING... </h2>;
if ( error ) return `Error! ${error}`;
if ( data !== undefined){
console.log("Interactions Data - " + data );
setInteractionsData(data.listInteractions.items)
return <div>
{interactionsData.map(({ direction, interaction, partner, duration, datetime, exhibit, organisation, file_name, datetime_added }, index) => (
<InteractionsCard
key={index}
Interaction={interaction}
Direction={direction}
Partner={partner}
Duration={duration}
DateTime={datetime}
Exhibit={exhibit}
Organisation={organisation}
FileName={file_name}
DateTimeAdded={datetime_added}
Pk={pk}
/>
))}
</div>
}
}
return (
<div style={{display: "inline-grid", inlineSize: "max-content", width: "100%"}}>
{/* <h2>🚀 Quest has identified the following results against {pk}</h2> */}
<center>
<ListAttributions pk={pk}/>
<ListInteractions pk={pk}/>
</center>
<br/>
<br />
{/* <ShowInteractionsDataGrid pk={pk}/> */}
{attributionsData !== [] &&
// Sep 14th 20:42 - No idea whats going on with this... The ListAttributions components above work fine
<TabbedResults pk={pk} attributionsData={attributionsData} interactionsData={interactionsData} />
}
<br />
<br />
</div>
);
}
ShowResults.propTypes = {
pk: PropTypes.string
};
export default ShowResults
a) You cant setSomeState and then use it immediately. It will not be immediately available within that same function. So the first time you try to map you will have the initialiser of the useState. In this case an empty array.
b) You should do the queries in the parent component, and pass them in as props so there is no need to set parent state
c) Its not good to be setting the state of a parent scope like this. You should move the children to different files (or at least outside of the component) and communicate with props and callbacks. You will save yourself a lot of trouble.
d) This will always be true so TabbedResults will always render:
attributionsData !== []
This is because [] makes a new array. You should use:
attributionsData && attributionsData.length
e) Never set state straight form the render function. Use a useEffect hook to set it based on the change of a property, such as your data suddenly being populated

React Native State Management Question - when does useState hook load?

I have a FlatList of items that has a "remove" button next to it.
When I click the remove button, I am able to remove the item from the backend BUT the actual list item is not removed from the view.
I am using useState hooks and it was to my understanding that the component re-renders after setState happens.
The setState function is used to update the state. It accepts a new
state value and enqueues a re-render of the component.
https://reactjs.org/docs/hooks-reference.html
What am I missing with how state is set and rendering?
I don't want to use the useEffect listener for various reasons. I want the component to re-render when the locations state is updated....which I am pretty sure is happening with my other setStates....not sure if I am totally missing the mark on what setState has been doing or if it's something specific about setLocations().
const [locations, setLocations] = useState(state.infoData.locations);
const [locationsNames, setLocationsNames] = useState(state.infoData.names]);
...
const removeLocationItemFromList = (item) => {
var newLocationsArray = locations;
var newLocationNameArray = locationsNames;
for(l in locations){
if(locations[l].name == item){
newLocationsArray.splice(l, 1);
newLocationNameArray.splice(l, 1);
} else {
console.log('false');
}
}
setLocationsNames(newLocationNameArray);
setLocations(newLocationsArray);
};
...
<FlatList style={{borderColor: 'black', fontSize: 16}}
data={locationNames}
renderItem={({ item }) =>
<LocationItem
onRemove={() => removeLocationItemFromList(item)}
title={item}/> }
keyExtractor={item => item}/>
UPDATED LOOP
const removeLocationItemFromList = (item) => {
var spliceNewLocationArray =locations;
var spliceNewLocationNameArray = locationsNames;
for(f in spliceNewLocationArray){
if(spliceNewLocationArray[f].name == item){
spliceNewLocationArray.splice(f, 1);
} else {
console.log('false');
}
}
for(f in spliceNewLocationNameArray){
if(spliceNewLocationNameArray[f] == item){
spliceNewLocationNameArray.splice(f, 1);
} else {
console.log('false');
}
}
var thirdTimesACharmName = spliceNewLocationNameArray;
var thirdTimesACharmLoc = spliceNewLocationArray;
console.log('thirdTimesACharmName:: ' + thirdTimesACharmName + ', thirdTimesACharmLoc::: ' + JSON.stringify(thirdTimesACharmLoc)); // I can see from this log that the data is correct
setLocationsNames(thirdTimesACharmName);
setLocations(thirdTimesACharmLoc);
};
This comes down to mutating the same locations array and calling setState with the same array again, which means that the FlatList which is a pure component will not re-render since the identity of locations has not changed. You could copy the locations array to newLocationsArray first (similarly with the newLocationNameArray) to avoid this.
var newLocationsArray = locations.slice();
var newLocationNameArray = locationsNames.slice();

react native conditional rendering with section list and flatlist

I have two different components one is CategoryList which is a flatlist and regionlist is a section list. I would like to display the CategoryList first and when item clicked the regionlist will show however I m not sure why it is not working. (I use context to store the state)
{!isToggle ? (
<CategoryList></CategoryList>
) : (
<RegionList style={styles.regionListStyle}></RegionList>
)}
I also create a button to see if it is a problem about the context but it is not.
const ToggleContext = createContext(true);
export const useToggle = () => {
return useContext(ToggleContext);
};
export function ToggleProvideData({children}) {
const [isToggle, setToggle] = useState(true)
return <ToggleContext.Provider value={{isToggle,setToggle}}>
{children}
</ToggleContext.Provider>;
}
I just wonder conditional render is it not working for flatlist?
UPDATE: I tried create a state to store the useContext isToggle but it only appears for like 1 sec
I guess isToggle as a boolean variable, hence you can use the below code for rendering conditionally
{ (isToggle === true) &&
<CategoryList></CategoryList>
}
{ (isToggle === false) &&
<RegionList style={styles.regionListStyle}></RegionList>
}

Can I use multiple or nested elements in the Label of a Picker Item in React Native?

I'm using React Native with NativeBase and would like to make the labels of my Picker more complicated than just one plain string of text.
But is it even possible to pass elements as the label, say multiple child elements wrapped in a single top-level element?
Or do Pickers only support plain text as labels?
As requested by bennygenel, here's a version of what I've tried:
export default class ThingPicker extends React.Component {
render() {
const {
initialThing,
things,
onThingChanged,
} = this.props;
const orderedThings = things.sort();
return (
<Picker
selectedValue={initialThing}
onValueChange={onThingChanged}>
{buildThingItems(orderedThings)}
</Picker>
);
}
}
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = th === "BMD" ? (<Text key={th} label={"foo"} value={"bar"}}>Hello</Text>)
: (<Picker.Item key={th} label={th} value={th} />);
return it;
});
}
Yes! It is possible, it just might not look very "right" for React/JSX code. Just create the elements you need and assign them to the label field:
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = (<Picker.Item
key={th}
label={currency === "BMD" ? (<Text>Hello</Text>) : th}
value={th} />);
return it;
});
}

React native navigator passing parent component to child

I don't know how to pass a reference to the TripList instance below to the AddTrip component. I need to do something like that to signal to TripList to refresh the data after adding a new trip.
In my render() method, inside <Navigator> I have:
if (route.index === 1) {
return <TripList
title={route.title}
onForward={ () => {
navigator.push({
title: 'Add New Trip',
index: 2,
});
}}
onBack={() => {
if (route.index > 0) {
navigator.pop();
}
}}
/>
} else {
return <AddTrip
styles={tripStyles}
title={route.title}
onBack={() => { navigator.pop(); }}
/>
}
However, when I call onBack() in AddTrip, after adding a trip, I want to call refresh() on TripList so the new trip is displayed. How best can I structure things to do that? I'm guessing I need to pass TripList somehow to AddTrip and then I can call refresh() there easily right before calling onBack().
This is not how React works. You don't pass instances of a component around, rather you pass the data to your component via props. And your component AddTrip should receive another props which is a function to call when adding a trip.
Let me illustrate this with a code example, this is not how your code should be in the end, but it'll illustrate how to contain the data outside of your components.
// Placed at the top of the file, not in a class or function.
let allTrips = [];
// Your navigator code.
if (route.index === 1) {
return <TripList
trips={allTrips}
title={route.title}
onForward={ () => {
navigator.push({
title: 'Add New Trip',
index: 2,
});
}}
onBack={() => {
if (route.index > 0) {
navigator.pop();
}
}} />
} else {
return <AddTrip
styles={tripStyles}
title={route.title}
onAdd={(tripData) => {
allTrips = [...allTrips, tripData];
}}
onBack={() => { navigator.pop(); }} />
}
As you can see, the logic about adding and finding the trips comes from the parent component, which is the navigator in this case. You will also note that we are reconstructing the content of allTrips, this is important as React is based on the concept of immutability.
You must have heard of Redux which is a system allowing all your components to discuss with a global store from which you fetch and save all your application state. It's a bit more complex that's why I did not use it as an example it.
I'll almost forget the most important! You will not need to signal to to your component that it needs refreshing, the magic of React should take care of it by itself!